iOS开发基础20-UITableView 动态更新:全局刷新、局部刷新、左滑操作与批量删除
UITableView 动态更新:全局刷新、局部刷新、左滑操作与批量删除
UITableView 的数据动态更新是开发中的常见需求。本文详细讲解全局刷新、局部刷新、左滑操作、编辑模式和批量删除的实现,并分析底层机制与性能优化。
一、前期准备
1. 数据模型
// XMGWine.h
@interface XMGWine : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *money;
@property (nonatomic, copy) NSString *image;
@property (nonatomic, assign, getter=isChecked) BOOL checked; // 自定义批量删除标记
@end
2. 自定义 Cell
// XMGWineCell.h
@interface XMGWineCell : UITableViewCell
@property (nonatomic, strong) XMGWine *wine;
+ (instancetype)cellWithTableView:(UITableView *)tableView;
@end
// XMGWineCell.m
@implementation XMGWineCell
+ (instancetype)cellWithTableView:(UITableView *)tableView {
static NSString *ID = @"wineCell";
XMGWineCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (!cell) {
// 使用 Subtitle 样式,包含 textLabel 和 detailTextLabel
cell = [[XMGWineCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
return cell;
}
- (void)setWine:(XMGWine *)wine {
_wine = wine;
self.textLabel.text = wine.name;
self.detailTextLabel.text = wine.money;
self.imageView.image = [UIImage imageNamed:wine.image];
self.accessoryType = wine.isChecked ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
}
@end
3. 控制器与懒加载数据
@interface ViewController () <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, strong) NSMutableArray<XMGWine *> *wineArray;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@end
@implementation ViewController
- (NSMutableArray<XMGWine *> *)wineArray {
if (!_wineArray) {
_wineArray = [NSMutableArray array];
// 从 plist 或网络加载初始数据
}
return _wineArray;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.dataSource = self;
self.tableView.delegate = self;
}
@end
二、全局刷新(reloadData)
reloadData 会重新加载整个 TableView,所有 Cell 重新创建和配置。
// 添加
- (IBAction)add {
XMGWine *wine = [[XMGWine alloc] init];
wine.money = @"20.5";
wine.name = @"很好喝的酒";
wine.image = @"new_wine";
[self.wineArray insertObject:wine atIndex:0];
[self.tableView reloadData];
}
// 删除
- (IBAction)remove {
if (self.wineArray.count > 0) {
[self.wineArray removeObjectAtIndex:0];
}
[self.tableView reloadData];
}
// 修改
- (IBAction)update {
if (self.wineArray.count > 0) {
XMGWine *wine = self.wineArray[0];
wine.money = @"100";
}
[self.tableView reloadData];
}
特点:实现简单,但会重新加载所有可见 Cell,无动画过渡,大数据量下性能差。适用于数据全量替换或小数据集。
三、局部刷新
局部刷新只更新变化的行,带有动画过渡,性能远优于全局刷新。
核心原则
- 先更新数据源,再更新视图。
- 用
beginUpdates/endUpdates包裹(iOS 11+ 推荐performBatchUpdates:completion:)。 - 插入/删除的 indexPaths 是更新前的位置。
1. 插入行
- (IBAction)add {
XMGWine *wine1 = [[XMGWine alloc] init];
wine1.name = @"很好喝的酒"; wine1.money = @"20.5"; wine1.image = @"new_wine";
XMGWine *wine2 = [[XMGWine alloc] init];
wine2.name = @"很好"; wine2.money = @"100.5"; wine2.image = @"new_wine";
NSArray *indexPaths = @[
[NSIndexPath indexPathForRow:0 inSection:0],
[NSIndexPath indexPathForRow:1 inSection:0]
];
[self.tableView performBatchUpdates:^{
[self.wineArray insertObjects:@[wine1, wine2] atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationRight];
} completion:nil];
}
2. 删除行
- (IBAction)remove {
if (self.wineArray.count < 2) return;
NSArray *indexPaths = @[
[NSIndexPath indexPathForRow:0 inSection:0],
[NSIndexPath indexPathForRow:1 inSection:0]
];
[self.tableView performBatchUpdates:^{
[self.wineArray removeObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]];
[self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationMiddle];
} completion:nil];
}
3. 刷新行(内容更新)
- (IBAction)update {
if (self.wineArray.count < 4) return;
self.wineArray[0].money = @"100";
self.wineArray[2].image = @"new_wine";
self.wineArray[3].image = @"new_wine";
NSArray *indexPaths = @[
[NSIndexPath indexPathForRow:0 inSection:0],
[NSIndexPath indexPathForRow:2 inSection:0],
[NSIndexPath indexPathForRow:3 inSection:0]
];
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationLeft];
}
4. 移动行
// 允许移动
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
// 移动完成后更新数据源
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
XMGWine *wine = self.wineArray[sourceIndexPath.row];
[self.wineArray removeObjectAtIndex:sourceIndexPath.row];
[self.wineArray insertObject:wine atIndex:destinationIndexPath.row];
}
beginUpdates vs performBatchUpdates
| 方式 | 引入版本 | 特点 |
|---|---|---|
beginUpdates / endUpdates |
iOS 2 | 同步执行,无 completion 回调 |
performBatchUpdates:completion: |
iOS 11 | 推荐使用,有 completion 回调,动画更流畅 |
四、左滑操作
1. 系统左滑删除(旧 API)
实现 commitEditingStyle:forRowAtIndexPath: 启用左滑删除:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[self.wineArray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
}
}
// 自定义删除按钮文字
- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath {
return @"删除";
}
// 控制哪些行可编辑(可选)
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
2. 左滑更多按钮(iOS 8~10:editActionsForRowAtIndexPath)
- (NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewRowAction *deleteAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@"删除" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
[self.wineArray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}];
UITableViewRowAction *followAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleNormal title:@"关注" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
NSLog(@"点击了关注");
[tableView setEditing:NO animated:YES];
}];
followAction.backgroundColor = [UIColor blueColor];
// 数组顺序:第一个是最靠近边缘的按钮(最右侧),最后一个是最左侧
return @[deleteAction, followAction];
}
3. 现代左滑 API(iOS 11+:推荐)
iOS 11 引入 UISwipeActionsConfiguration 和 UIContextualAction,替代已废弃的 editActionsForRowAtIndexPath::
- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath {
UIContextualAction *deleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:@"删除" handler:^(UIContextualAction *action, UIView *sourceView, void (^completionHandler)(BOOL)) {
[self.wineArray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
completionHandler(YES);
}];
UIContextualAction *followAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:@"关注" handler:^(UIContextualAction *action, UIView *sourceView, void (^completionHandler)(BOOL)) {
NSLog(@"关注");
completionHandler(YES);
}];
followAction.backgroundColor = [UIColor blueColor];
UISwipeActionsConfiguration *config = [UISwipeActionsConfiguration configurationWithActions:@[deleteAction, followAction]];
config.performsFirstActionWithFullSwipe = YES; // 满滑直接执行第一个动作(删除)
return config;
}
左侧滑按钮使用
leadingSwipeActionsConfigurationForRowAtIndexPath:。
五、编辑模式与批量删除
1. 进入编辑模式
- (IBAction)toggleEditing {
[self.tableView setEditing:!self.tableView.isEditing animated:YES];
}
编辑模式下,每行左侧出现删除控制(红色圆点),点击后出现删除按钮。
2. 系统批量删除(多选)
- (void)viewDidLoad {
[super viewDidLoad];
// 编辑模式下允许多选
self.tableView.allowsMultipleSelectionDuringEditing = YES;
}
- (IBAction)batchDelete {
NSArray<NSIndexPath *> *selectedIndexPaths = self.tableView.indexPathsForSelectedRows;
if (selectedIndexPaths.count == 0) return;
// 按 row 降序排列,避免删除时索引错乱
NSArray *sortedPaths = [selectedIndexPaths sortedArrayUsingComparator:^NSComparisonResult(NSIndexPath *obj1, NSIndexPath *obj2) {
return obj2.row < obj1.row; // 降序
}];
[self.tableView performBatchUpdates:^{
for (NSIndexPath *indexPath in sortedPaths) {
[self.wineArray removeObjectAtIndex:indexPath.row];
}
[self.tableView deleteRowsAtIndexPaths:selectedIndexPaths withRowAnimation:UITableViewRowAnimationLeft];
} completion:^(BOOL finished) {
[self.tableView setEditing:NO animated:YES];
}];
}
关键:批量删除数据源时,必须按索引降序删除。如果升序删除,删除第 0 条后,原来的第 1 条变成第 0 条,再删除第 1 条就会跳过或崩溃。
3. 自定义批量删除(非编辑模式)
不依赖系统编辑模式,通过模型的 checked 属性手动管理选中状态:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
XMGWine *wine = self.wineArray[indexPath.row];
wine.checked = !wine.isChecked;
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
- (IBAction)customBatchDelete {
NSMutableArray<XMGWine *> *toDelete = [NSMutableArray array];
for (XMGWine *wine in self.wineArray) {
if (wine.isChecked) {
[toDelete addObject:wine];
}
}
if (toDelete.count == 0) return;
[self.wineArray removeObjectsInArray:toDelete];
// 小数据量用 reloadData,大数据量建议用 deleteRows 局部刷新
[self.tableView reloadData];
}
六、Swift 版本示例
左滑操作(现代 API)
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "删除") { [weak self] (_, _, completion) in
guard let self = self else { return }
self.wineArray.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .left)
completion(true)
}
let followAction = UIContextualAction(style: .normal, title: "关注") { (_, _, completion) in
print("关注")
completion(true)
}
followAction.backgroundColor = .blue
let config = UISwipeActionsConfiguration(actions: [deleteAction, followAction])
config.performsFirstActionWithFullSwipe = true
return config
}
批量删除
@IBAction func batchDelete() {
guard let indexPaths = tableView.indexPathsForSelectedRows, !indexPaths.isEmpty else { return }
let sortedPaths = indexPaths.sorted { $0.row > $1.row } // 降序
tableView.performBatchUpdates({
for indexPath in sortedPaths {
wineArray.remove(at: indexPath.row)
}
tableView.deleteRows(at: indexPaths, with: .left)
}) { _ in
self.tableView.setEditing(false, animated: true)
}
}
七、底层逻辑与性能优化
局部刷新的底层机制
beginUpdates/endUpdates将多个插入、删除、移动操作合并为一个动画批次。- TableView 在
endUpdates时统一计算 indexPath 的映射关系,执行动画。 - 插入/删除的 indexPaths 是更新前的位置,系统内部会自动处理索引偏移。
性能优化建议
| 场景 | 推荐方案 |
|---|---|
| 全量数据替换 | reloadData |
| 少量行增删改 | 局部刷新(insert/delete/reloadRows) |
| 批量操作 | performBatchUpdates 包裹 |
| 左滑删除 | iOS 11+ UISwipeActionsConfiguration |
| 批量删除 | 系统多选 + 降序删除数据源 |
| 大数据量 | 避免 reloadData,优先局部刷新 |
常见坑
- 数据源与视图不同步:必须先更新数据源,再调用增删行方法,否则会崩溃(
NSInternalInconsistencyException)。 - 批量删除索引错乱:必须按降序删除数据源,或先收集要删除的对象再统一移除。
- 左滑与编辑模式冲突:左滑删除(
trailingSwipeActionsConfiguration)和编辑模式删除可以共存,但需注意交互逻辑。 reloadRows与reloadData:reloadRows只刷新指定行,保留其他行的状态(如滚动位置);reloadData重置一切。
八、总结
- 全局刷新(
reloadData)简单但低效,适合全量替换。 - 局部刷新(
insertRows/deleteRows/reloadRows)性能好、有动画,是推荐方案,用performBatchUpdates包裹。 - 左滑操作:iOS 11+ 推荐
UISwipeActionsConfiguration+UIContextualAction,旧 APIeditActionsForRowAtIndexPath:已废弃。 - 批量删除:系统多选模式(
allowsMultipleSelectionDuringEditing)或自定义checked模型;删除数据源时按降序操作避免索引错乱。 - 核心原则:先更新数据源,再更新视图;保证数据源与视图状态一致。
将来的你会感谢今天如此努力的你!
版权声明:本文为博主原创文章,未经博主允许不得转载。

浙公网安备 33010602011771号