MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C 在 iOS 表格视图(UITableView)中的应用

2023-06-032.3k 阅读

UITableView 基础概述

在 iOS 开发中,UITableView 是一个至关重要的用户界面组件,它用于以表格形式展示数据列表。无论是简单的设置选项列表,还是复杂的社交媒体动态流,UITableView 都能胜任。它不仅提供了高效的滚动机制,允许用户在大量数据中轻松导航,还支持丰富的交互功能,如单元格的选择、编辑等。

UITableView 的结构

UITableView 由多个单元格(UITableViewCell)组成,这些单元格按行排列。每一行可以包含不同类型的内容,比如文本、图片等。此外,UITableView 还支持分区(section)的概念,通过分区可以将数据进行逻辑分组。例如,在联系人应用中,可以按字母分区展示联系人列表。

UITableView 的数据源和代理

要让 UITableView 正常工作,必须设置其数据源(dataSource)和代理(delegate)。数据源负责提供表格显示所需的数据,代理则负责处理表格的各种交互行为,如单元格的点击、编辑等。在 Objective-C 中,通常是在视图控制器(UIViewController)中实现数据源和代理协议。

在项目中引入 UITableView

创建 UITableView 实例

在视图控制器的 viewDidLoad 方法中,可以通过代码创建 UITableView 实例。以下是基本的创建代码:

#import "ViewController.h"

@interface ViewController () <UITableViewDataSource, UITableViewDelegate>

@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSArray *dataArray;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建 UITableView 实例
    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
    self.tableView.dataSource = self;
    self.tableView.delegate = self;
    [self.view addSubview:self.tableView];
    
    // 初始化数据
    self.dataArray = @[@"Item 1", @"Item 2", @"Item 3"];
}

在上述代码中,首先创建了一个 UITableView 实例,并设置其框架为视图控制器的视图边界。然后设置了数据源和代理为当前视图控制器,并将其添加到视图中。同时初始化了一个简单的数组作为表格的数据来源。

注册单元格类

在使用 UITableView 之前,需要注册单元格类。这样 UITableView 才能知道如何创建和复用单元格。有两种常见的注册方式:注册类和注册 nib 文件。

注册类

// 注册单元格类
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"CellIdentifier"];

这里注册了系统默认的 UITableViewCell 类,并指定了一个重用标识符(reuseIdentifier)。在后续创建单元格时,将使用这个标识符来复用单元格,以提高性能。

注册 nib 文件

如果单元格有自定义的布局,通常会使用 nib 文件来设计单元格。首先创建一个 nib 文件,然后在代码中注册:

UINib *nib = [UINib nibWithName:@"CustomCell" bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:@"CustomCellIdentifier"];

这里假设创建了一个名为 CustomCell.xib 的 nib 文件,并注册它,同样指定了一个重用标识符。

实现数据源方法

确定分区数量

数据源协议中的 numberOfSectionsInTableView: 方法用于确定表格的分区数量。如果不需要分区,直接返回 1 即可。

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

确定每个分区的行数

tableView:numberOfRowsInSection: 方法用于确定每个分区中的行数。通常根据数据数组的数量来返回。

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.dataArray.count;
}

创建单元格

tableView:cellForRowAtIndexPath: 方法是最重要的数据源方法之一,它负责为每一行创建并返回一个单元格。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellIdentifier" forIndexPath:indexPath];
    cell.textLabel.text = self.dataArray[indexPath.row];
    return cell;
}

在上述代码中,首先通过 dequeueReusableCellWithIdentifier:forIndexPath: 方法获取一个可复用的单元格。如果没有可复用的单元格,系统会根据之前注册的类或 nib 文件创建一个新的单元格。然后设置单元格文本标签的文本为数据数组中对应行的数据。

处理单元格交互

处理单元格选中

通过实现代理协议中的 tableView:didSelectRowAtIndexPath: 方法,可以处理用户点击单元格的行为。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString *selectedItem = self.dataArray[indexPath.row];
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Selected Item" message:selectedItem preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
    [alertController addAction:okAction];
    [self presentViewController:alertController animated:YES completion:nil];
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

在这个方法中,首先获取用户点击的单元格对应的项目,然后创建一个警告框显示选中的项目。最后通过 deselectRowAtIndexPath:animated: 方法取消单元格的选中状态,使界面更友好。

编辑单元格

进入编辑模式

可以通过设置 UITableViewediting 属性来进入编辑模式。通常在视图控制器中添加一个编辑按钮,点击按钮进入编辑模式。

UIBarButtonItem *editButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:@selector(toggleEditMode)];
self.navigationItem.rightBarButtonItem = editButton;

然后实现 toggleEditMode 方法:

- (void)toggleEditMode {
    if (self.tableView.isEditing) {
        [self.tableView setEditing:NO animated:YES];
        self.navigationItem.rightBarButtonItem.title = @"Edit";
    } else {
        [self.tableView setEditing:YES animated:YES];
        self.navigationItem.rightBarButtonItem.title = @"Done";
    }
}

实现编辑操作

在代理协议中实现 tableView:commitEditingStyle:forRowAtIndexPath: 方法来处理编辑操作,如删除单元格。

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        NSMutableArray *mutableArray = [self.dataArray mutableCopy];
        [mutableArray removeObjectAtIndex:indexPath.row];
        self.dataArray = [mutableArray copy];
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
    }
}

在这个方法中,如果编辑风格是删除,首先创建数据数组的可变副本,然后从副本中移除对应行的数据。接着更新数据数组,并使用 deleteRowsAtIndexPaths:withRowAnimation: 方法从表格中删除对应的单元格,同时添加一个淡入淡出的动画效果。

自定义 UITableViewCell

创建自定义单元格类

首先创建一个继承自 UITableViewCell 的自定义类,例如 CustomCell.hCustomCell.m

// CustomCell.h
#import <UIKit/UIKit.h>

@interface CustomCell : UITableViewCell

@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIImageView *iconImageView;

@end
// CustomCell.m
#import "CustomCell.h"

@implementation CustomCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 200, 20)];
        [self.contentView addSubview:self.titleLabel];
        
        self.iconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(220, 10, 40, 40)];
        [self.contentView addSubview:self.iconImageView];
    }
    return self;
}

@end

在上述代码中,定义了一个自定义单元格类 CustomCell,包含一个标题标签和一个图标图像视图。在初始化方法中,设置了它们的位置和大小,并添加到单元格的内容视图中。

使用自定义单元格

在视图控制器中注册自定义单元格的 nib 文件,并在 tableView:cellForRowAtIndexPath: 方法中使用自定义单元格。

// 注册 nib 文件
UINib *nib = [UINib nibWithName:@"CustomCell" bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:@"CustomCellIdentifier"];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCellIdentifier" forIndexPath:indexPath];
    cell.titleLabel.text = self.dataArray[indexPath.row];
    cell.iconImageView.image = [UIImage imageNamed:@"icon.png"];
    return cell;
}

这里从表格中获取自定义单元格,并设置标题标签的文本和图标图像视图的图像。

优化 UITableView 的性能

重用单元格

重用单元格是提高 UITableView 性能的关键。通过 dequeueReusableCellWithIdentifier:forIndexPath: 方法,系统会尝试从复用队列中获取一个可复用的单元格。如果队列中没有可用的单元格,才会根据注册的类或 nib 文件创建新的单元格。这大大减少了内存开销和创建单元格的时间。

异步加载数据

当表格数据包含图片或其他需要网络加载的内容时,应该使用异步加载。可以使用 NSURLSession 或第三方库如 AFNetworkingSDWebImage 等。例如,使用 SDWebImage 加载图片:

#import <SDWebImage/SDWebImage.h>

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCellIdentifier" forIndexPath:indexPath];
    cell.titleLabel.text = self.dataArray[indexPath.row];
    [cell.iconImageView sd_setImageWithURL:[NSURL URLWithString:@"http://example.com/icon.png"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
    return cell;
}

这样在滚动表格时,图片会在后台异步加载,不会阻塞主线程,保证了滚动的流畅性。

减少单元格布局计算

尽量简化单元格的布局,避免在 tableView:cellForRowAtIndexPath: 方法中进行复杂的布局计算。可以在自定义单元格类的初始化方法中一次性设置好布局,然后在 tableView:cellForRowAtIndexPath: 方法中只更新数据。

UITableView 的高级应用

分组表格

通过在数据源方法中返回不同的分区数量和每个分区的行数,可以创建分组表格。在 tableView:titleForHeaderInSection: 方法中设置每个分区的标题。

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 3;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (section == 0) {
        return 2;
    } else if (section == 1) {
        return 3;
    } else {
        return 1;
    }
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if (section == 0) {
        return @"Section 1";
    } else if (section == 1) {
        return @"Section 2";
    } else {
        return @"Section 3";
    }
}

索引表格

为表格添加索引可以方便用户快速定位数据。实现数据源协议中的 sectionIndexTitlesForTableView:tableView:sectionForSectionIndexTitle:atIndex: 方法。

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return @[@"A", @"B", @"C"];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    if ([title isEqualToString:@"A"]) {
        return 0;
    } else if ([title isEqualToString:@"B"]) {
        return 1;
    } else {
        return 2;
    }
}

在上述代码中,定义了索引标题数组,并根据索引标题返回对应的分区。

动态更新表格数据

在应用中,数据可能会动态变化,例如添加新数据或更新现有数据。可以通过调用 UITableViewreloadData 方法重新加载所有数据,或者使用更细粒度的方法如 insertRowsAtIndexPaths:withRowAnimation:deleteRowsAtIndexPaths:withRowAnimation:reloadRowsAtIndexPaths:withRowAnimation: 等。

// 添加新数据
NSMutableArray *mutableArray = [self.dataArray mutableCopy];
[mutableArray addObject:@"New Item"];
self.dataArray = [mutableArray copy];
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:self.dataArray.count - 1 inSection:0];
[self.tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];

这里添加了一个新的数据项,并通过 insertRowsAtIndexPaths:withRowAnimation: 方法将新行插入表格,同时添加一个自动动画效果。

通过以上对 Objective-CiOS 表格视图(UITableView)中的应用的详细介绍,从基础的创建、数据填充、交互处理到性能优化和高级应用,开发者可以全面掌握如何在 iOS 应用中高效地使用 UITableView 来展示和处理数据。在实际开发中,根据具体需求灵活运用这些知识,可以创建出功能强大且用户体验良好的应用界面。