版本
Xcode 9.1
UITableView : UIScrollView : UIView : UIResponder : NSObject
从继承关系我们可以知道:UITableView可以滚动,具有view的性质,能响应UI事件等等~
UITableView可以有很多个section(本文成为组),每个section分别由一个Header(组头)、一个Fooder(组尾)及若干个cell组成。
typedef NS_ENUM(NSInteger, UITableViewStyle) {
UITableViewStylePlain, // regular table view
UITableViewStyleGrouped // preferences style table view
};
主要区别为:
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
这四种风格分别对应storyboard/XIB里面的设置:
每种风格对应效果图如下:
如果你的UITableViewCell不显示image或者detailTextLabel,那么最好先检查一下注册的时候设置的style是否符合。
为什么要重用cell?
由于屏幕显示的cell有限,而当数据量大的时候,如果每个数据创建一个Cell,就会占很大内存。
重用cell机制是这样的:创建了屏幕内cell所需的个数+1个屏幕之外的cell,在满足显示效果的前提下尽可能降低消耗内存。
这种机制下默认有一个可变数组NSMutableArray* visiableCells用来保存当前显示的cell;一个可变字典NSMutableDictnery* reusableTableCells用来保存可重复利用的cell(之所以用字典是因为可重用的cell有不止一种样式,我们需要根据它的reuseIdentifier,也就是所谓的重用标示符来查找是否有可重用的该样式的cell)。
如何重用cell?
首先要创建一个有重用标识符reuseIdentifier的cell,这些cell会被保存到重用队列中去。当需要实例化cell的时候,首先根据reuseIdentifier去找对应的cell。
加载cell到重用队列有两种方法:
// 法1:使用tableView注册一个cell
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"myCell"];
// 法2:在cell的配置代理方法中创建一个cell
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
}
根据重用标识符reuseIdentifier实例化一个cell:
// 实例化一个UITableViewCell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myCell" forIndexPath:indexPath];
效果图:
代码比较简单,基本上都有注释,直接上完整代码:
#define TABLE_WIDTH [UIScreen mainScreen].bounds.size.width
#define TABLE_HEIGHT [UIScreen mainScreen].bounds.size.height
#import "ViewController.h"
@interface ViewController () <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong, nullable) NSArray *digitalArr;
@property (nonatomic, strong, nullable) NSArray *letterArr;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 装载数据源
self.digitalArr = @[@"0", @"1", @"2"];
self.letterArr = @[@"A", @"B", @"C"];
// 添加tableView到self.view
[self.view addSubview:self.tableView];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - UITableViewDelegate
// cell高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 60;
}
// Header高度
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 30;
}
// Footer高度
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 30;
}
// 选中了某个cell
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
#pragma mark - UITableViewDataSource
// Section数量
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
// 对应Section中cell的个数
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
switch (section) {
case 0: // 数字组
return self.digitalArr.count;
break;
case 1: // 字母组
return self.letterArr.count;
break;
default:
break;
}
return 0;
}
// Header标题
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
switch (section) {
case 0: // 数字组
return @"数字组头";
break;
case 1: // 字母组
return @"字母组头";
break;
default:
break;
}
return nil;
}
// Footer标题
- (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
switch (section) {
case 0: // 数字组
return @"数字组尾";
break;
case 1: // 字母组
return @"字母组尾";
break;
default:
break;
}
return nil;
}
// cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// 实例化一个UITableViewCell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myCell" forIndexPath:indexPath];
// 由于已经注册过重用cell,所以这里不必判断是否存在
// if (cell == nil) {
// cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
// }
// 设置cell相关属性
switch (indexPath.section) {
case 0: // 数字组
if (indexPath.row < self.digitalArr.count) {
cell.textLabel.text = self.digitalArr[indexPath.row];
}
break;
case 1: // 字母组
if (indexPath.row < self.letterArr.count) {
cell.textLabel.text = self.letterArr[indexPath.row];
}
break;
default:
break;
}
// 返回cell
return cell;
}
#pragma mark - 懒加载
- (UITableView *)tableView {
if (_tableView == nil) {
// 实例化一个UITableView
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, TABLE_WIDTH, TABLE_HEIGHT)
style:UITableViewStylePlain];
// 注册一个cell
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"myCell"];
// 设置代理
_tableView.delegate = self;
_tableView.dataSource = self;
}
return _tableView;
}
@end
当然,这里要说的操作并不是先改变数据源然后reloadData,而是通过对cell的操作,使整个过程具有动画效果、显得顺畅。
我们会新增两个代理,第一个是返回编辑操作的类型。有插入类型和删除类型。
先定义一个全局变量_isDelete用于标记是点击添加还是删除按钮:
- (IBAction)btnAdd:(UIButton *)sender {
_isDelete = NO; // 编辑类型:添加
// 启动编辑(参数一:tableView是否正在编辑;参数二:是否动画)
[self.tableView setEditing:!self.tableView.isEditing animated:YES];
}
调用方法setEditing: animated:后,接着来到代理方法:
// 返回操作类型(添加/删除)
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"返回操作类型");
if (_isDelete) {
return UITableViewCellEditingStyleDelete;
}
return UITableViewCellEditingStyleInsert;
}
当点击左边绿色“+”号按钮后,调用此代理方法:
// 提交编辑操作
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"提交编辑操作");
if (editingStyle == UITableViewCellEditingStyleInsert) {
switch (indexPath.section) {
case 0:
[self.digitalArr insertObject:@"我是新插入的数字" atIndex:indexPath.row];
break;
case 1:
[self.letterArr insertObject:@"我是新插入的字母" atIndex:indexPath.row];
break;
default:
break;
}
// 与reloadData不同,此方法带动画效果.(参数二为动画类型)
[tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
}
}
与添加类似,假如我们设置_isDelete = YES,那么代理返回的是UITableViewCellEditingStyleDelete类型,这时删除是这样子的:
你也不喜欢这种做法对不对?那我们换个做法,直接左滑删除某个cell或者点编辑多选/全选然后批量删除cell。
滑动删除
其实,只要你返回UITableViewCellEditingStyleDelete类型,并实现代理方法tableView : commitEditingStyle: forRowAtIndexPath:,然后就可以向左滑动调出删除按钮了。但是这样一来点击添加按钮就不能调出绿色“+”号按钮了,要解决这个问题,需要修改之前的返回类型的代理方法:
// 返回操作类型(添加/删除)
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
if (!tableView.isEditing) {
return UITableViewCellEditingStyleDelete;
}
return UITableViewCellEditingStyleInsert;
}
// 提交编辑操作
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"提交编辑操作");
if (editingStyle == UITableViewCellEditingStyleInsert) {
switch (indexPath.section) {
case 0:
[self.digitalArr insertObject:@"我是新插入的数字" atIndex:indexPath.row];
break;
case 1:
[self.letterArr insertObject:@"我是新插入的字母" atIndex:indexPath.row];
break;
default:
break;
}
// 与reloadData不同,此方法带动画效果.(参数二为动画类型)
[tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
}else if (editingStyle == UITableViewCellEditingStyleDelete) {
switch (indexPath.section) {
case 0:
if (indexPath.row < self.digitalArr.count) {
[self.digitalArr removeObjectAtIndex:indexPath.row];
}
break;
case 1:
if (indexPath.row < self.letterArr.count) {
[self.letterArr removeObjectAtIndex:indexPath.row];
}
break;
default:
break;
}
// 与reloadData不同,此方法带动画效果.(参数二为动画类型)
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
}
}
批量删除
改的地方比较多,直接上完整代码了。要注意的是,当section中没有cell时该section也应该删掉,本代码就不演示了。
#define TABLE_WIDTH [UIScreen mainScreen].bounds.size.width
#define TABLE_HEIGHT [UIScreen mainScreen].bounds.size.height
#import "ViewController.h"
@interface ViewController () <UITableViewDelegate, UITableViewDataSource> {
BOOL _isDelete; // 用于区别是添加还是删除操作
NSMutableArray *_deleteArr; // 用于储存将要删除的cell的indexPath
}
@property (weak, nonatomic) IBOutlet UIButton *editBtn;
@property (weak, nonatomic) IBOutlet UIButton *addBtn;
@property (weak, nonatomic) IBOutlet UIView *bottomView;
@property (weak, nonatomic) IBOutlet UIButton *allBtn;
@property (weak, nonatomic) IBOutlet UIButton *deleteBtn;
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong, nullable) NSMutableArray *digitalArr;
@property (nonatomic, strong, nullable) NSMutableArray *letterArr;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_isDelete = YES; // 编辑类型:删除
self.bottomView.hidden = YES; //隐藏底部view
self.deleteBtn.hidden = YES; //隐藏删除按钮
// 装载数据源
self.digitalArr = [@[@"0", @"1", @"2"] mutableCopy];
self.letterArr = [@[@"A", @"B", @"C"] mutableCopy];
// 添加tableView到self.view
[self.view addSubview:self.tableView];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - UITableViewDelegate
// cell高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 60;
}
// Header高度
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 30;
}
// Footer高度
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 30;
}
// 选中了某个cell
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// 判断是否是多选操作
if (self.tableView.editing) {
if (_deleteArr == nil) {
_deleteArr = [NSMutableArray new];
}
// 将选中cell的indexPath添加到删除数组deleteArr
[_deleteArr addObject:indexPath];
// 显示删除按钮
self.deleteBtn.hidden = NO;
return;
}
}
// 取消选中时 将存放在deleteArr中的数据移除
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
// 判断是否是多选操作
if (self.tableView.editing) {
//将取消选中的cell的indexPath从数组deleteArr中移除
[_deleteArr removeObject:indexPath];
//隐藏删除按钮
if ([_deleteArr count] == 0) {
_deleteBtn.hidden = YES;
}
}
}
// 返回操作类型(添加/删除)
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"返回操作类型");
if (!tableView.isEditing) {
return UITableViewCellEditingStyleDelete;
}
return UITableViewCellEditingStyleInsert;
}
// 提交编辑操作
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"提交编辑操作");
if (editingStyle == UITableViewCellEditingStyleInsert) {
switch (indexPath.section) {
case 0:
[self.digitalArr insertObject:@"我是新插入的数字" atIndex:indexPath.row];
break;
case 1:
[self.letterArr insertObject:@"我是新插入的字母" atIndex:indexPath.row];
break;
default:
break;
}
// 与reloadData不同,此方法带动画效果.(参数二为动画类型)
[tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
}else if (editingStyle == UITableViewCellEditingStyleDelete) {
switch (indexPath.section) {
case 0:
if (indexPath.row < self.digitalArr.count) {
[self.digitalArr removeObjectAtIndex:indexPath.row];
}
break;
case 1:
if (indexPath.row < self.letterArr.count) {
[self.letterArr removeObjectAtIndex:indexPath.row];
}
break;
default:
break;
}
// 与reloadData不同,此方法带动画效果.(参数二为动画类型)
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
}
}
#pragma mark - UITableViewDataSource
// Section数量
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
// 对应Section中cell的个数
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
switch (section) {
case 0: // 数字组
return self.digitalArr.count;
break;
case 1: // 字母组
return self.letterArr.count;
break;
default:
break;
}
return 0;
}
// Header标题
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
switch (section) {
case 0: // 数字组
return @"数字组头";
break;
case 1: // 字母组
return @"字母组头";
break;
default:
break;
}
return nil;
}
// Footer标题
- (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
switch (section) {
case 0: // 数字组
return @"数字组尾";
break;
case 1: // 字母组
return @"字母组尾";
break;
default:
break;
}
return nil;
}
// cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// 实例化一个UITableViewCell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myCell" forIndexPath:indexPath];
// 由于已经注册过重用cell,所以这里不必判断是否存在
// if (cell == nil) {
// cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
// }
// 设置cell相关属性
switch (indexPath.section) {
case 0: // 数字组
if (indexPath.row < self.digitalArr.count) {
cell.textLabel.text = self.digitalArr[indexPath.row];
}
break;
case 1: // 字母组
if (indexPath.row < self.letterArr.count) {
cell.textLabel.text = self.letterArr[indexPath.row];
}
break;
default:
break;
}
// 返回cell
return cell;
}
#pragma mark - UI事件
- (IBAction)btnEdit:(UIButton *)sender {
// 如果当前有别人的编辑操作,取消别人的编辑操作,并退出
if (self.tableView.isEditing && !sender.selected) {
// 启动编辑(参数一:tableView是否正在编辑;参数二:是否动画)
[self.tableView setEditing:!self.tableView.isEditing animated:YES];
return;
}
// 编辑时显示底部view、不显示添加按钮
sender.selected = !sender.selected;
if (sender.selected) {
self.addBtn.hidden = YES;
self.deleteBtn.hidden = YES;
self.deleteBtn.selected = NO;
self.allBtn.selected = NO;
self.bottomView.hidden = NO;
}else {
// 将要隐藏底部view时,还原各种btn
self.addBtn.hidden = NO;
self.deleteBtn.hidden = YES;
self.deleteBtn.selected = NO;
self.allBtn.selected = NO;
self.bottomView.hidden = YES;
}
// 支持同时选中多行
self.tableView.allowsMultipleSelectionDuringEditing = YES;
// 启动编辑(参数一:tableView是否正在编辑;参数二:是否动画)
[self.tableView setEditing:!self.tableView.isEditing animated:YES];
}
- (IBAction)btnAdd:(UIButton *)sender {
// 如果当前有别人的编辑操作,取消别人的编辑操作,并退出
if (self.tableView.isEditing && !sender.selected) {
// 启动编辑(参数一:tableView是否正在编辑;参数二:是否动画)
[self.tableView setEditing:!self.tableView.isEditing animated:YES];
return;
}
// 编辑时显示底部view、不显示添加按钮
sender.selected = !sender.selected;
if (sender.selected) {
self.editBtn.hidden = YES;
}else {
self.editBtn.hidden = NO;
}
_isDelete = NO; // 编辑类型:添加
// 取消支持同时选中多行
self.tableView.allowsMultipleSelectionDuringEditing = NO;
// 启动编辑(参数一:tableView是否正在编辑;参数二:是否动画)
[self.tableView setEditing:!self.tableView.isEditing animated:YES];
}
- (IBAction)btnAll:(UIButton *)sender {
sender.selected = !sender.selected;
if (sender.selected) {
// 遍历选中数字cell
for (int i=0; i<self.digitalArr.count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
// 选中TableView中的cell
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionTop];
// 将选中的cell的indexPath存入数组deleteArr
if (_deleteArr == nil) {
_deleteArr = [NSMutableArray new];
}
[_deleteArr addObject:indexPath];
}
// 遍历选中字母cell
for (int i=0; i<self.letterArr.count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:1];
// 选中TableView中的cell
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionTop];
// 将选中的cell的indexPath存入数组deleteArr
if (_deleteArr == nil) {
_deleteArr = [NSMutableArray new];
}
[_deleteArr addObject:indexPath];
}
// 显示删除按钮
self.deleteBtn.hidden = NO;
}else {
// 遍历取消选中数字cell
for (int i=0; i<self.digitalArr.count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
//取消选中TableView中的cell
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
// 遍历取消选中字母cell
for (int i=0; i<self.letterArr.count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:1];
//取消选中TableView中的cell
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
// 移除数组deleteArr中所有元素(indexPath)
if (_deleteArr) {
[_deleteArr removeAllObjects];
}
//隐藏删除按钮
self.deleteBtn.hidden = YES;
}
}
- (IBAction)btnDelete:(UIButton *)sender {
if (self.tableView.isEditing && _deleteArr) {
// 此处运用i--是为了防止removeObjectAtIndex出错
for (NSInteger i=_deleteArr.count-1; i>=0; i--) {
NSIndexPath *indexPath = _deleteArr[i];
// 数据源同步删除
switch (indexPath.section) {
case 0: // 数字
if (indexPath.row < self.digitalArr.count) {
[self.digitalArr removeObjectAtIndex:indexPath.row];
}
break;
case 1: // 字母
if (indexPath.row < self.letterArr.count) {
[self.letterArr removeObjectAtIndex:indexPath.row];
}
break;
default:
break;
}
// 与reloadData不同,此方法带动画效果.(参数二为动画类型)
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
}
[_deleteArr removeAllObjects];
}
}
#pragma mark - 懒加载
- (UITableView *)tableView {
if (_tableView == nil) {
// 实例化一个UITableView
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64, TABLE_WIDTH, TABLE_HEIGHT-64-46)
style:UITableViewStylePlain];
// 注册一个cell
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"myCell"];
// 设置代理
_tableView.delegate = self;
_tableView.dataSource = self;
}
return _tableView;
}
@end
和增加、删除相似,要实现排序功能只需实现下面这个协议即可:
/**
移动cell
@param tableView 正在操作的tableView对象
@param sourceIndexPath 移动前的indexPath
@param destinationIndexPath 移动后的indexPath
*/
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
// 需要改变数据源的排序(略)
}
常用cell的创建方法有两种,一种是使用系统cell,另一种是自定义cell。其中自定义cell又可以分为三种:纯代码创建cell,XIB创建cell,storyboard创建cell。
- 使用系统cell
- 自定义cell之纯代码
- 自定义cell之XIB
- 自定义cell之storyboard
其实我们上面例子中使用的就是系统cell:通过注册一个UITableViewCell,然后在tableView: cellForRowAtIndexPath:方法里返回一个已经注册到重用队列里的UITableViewCell。
// 注册一个cell
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"myCell"];
// 在tableView: cellForRowAtIndexPath:方法里实例化一个UITableViewCell,并返回
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myCell" forIndexPath:indexPath];
return cell;
首先,我们先新建一个继承自UITableViewCell的类MyTableViewCell:
然后倒入头文件#import “MyTableViewCell.h”,接下来将上面的系统cell改为我们新建的自定义cell就好了:
// 注册一个cell
[_tableView registerClass:[MyTableViewCell class] forCellReuseIdentifier:@"myCell"];
// 在tableView: cellForRowAtIndexPath:方法里实例化一个MyTableViewCell,并返回
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myCell" forIndexPath:indexPath];
return cell;
我们可以重写一下cell,来验证是否是使用自定义cell。因为cell继承自UIView,因此我们可以加入如下方法:
- (void)drawRect:(CGRect)rect {
self.backgroundColor = [UIColor redColor];
}
运行结果:
和纯代码创建类似,只需要在创建的时候勾选创建XIB文件:
然后我们就会看到XIB里面cell已经自动关联到类MyXibTableViewCell了:
当然,如果我们创建的时候不勾选Also create XIB file选项,也是可以后续单独创建XIB的,只是创建好的XIB要手动关联到我们自定义cell的类名。
接下来,我们为XIB的cell起一个标识符:
最后,我们在注册cell的时候,使用这个标识符找到XIB中的cell,从而创建使用。
// 注册一个cell
[_tableView registerNib:[UINib nibWithNibName:@"MyXibTableViewCell" bundle:nil] forCellReuseIdentifier:@"MyXibTableViewCell"];
在tableView: cellForRowAtIndexPath:方法里实例化一个MyXibTableViewCell,并返回:
// 返回MyXibTableViewCell
- (MyXibTableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// 实例化一个MyXibTableViewCell
MyXibTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyXibTableViewCell"];
// 设置cell相关属性
// 返回cell
return cell;
}
为了验证是否使用XIB的cell,设置cell的背景颜色:
运行结果:
先在Main.storyboard里面创建一个tableview和一个cell:
设置tableview的dalegate和datasource:
设置类别:
设置id:
最后,我们可以省掉注册cell这一步(因为系统会自动帮我们注册,噢耶~),直接在tableView: cellForRowAtIndexPath:方法里实返回MyStoryboardTableViewCell
// cell
- (MyStoryboardTableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// 实例化一个MyStoryboardTableViewCell
MyStoryboardTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyStoryboardTableViewCell"];
// 设置cell相关属性
if (indexPath.row < self.digitalArr.count) {
cell.textLabel.text = self.digitalArr[indexPath.row];
}
// 返回cell
return cell;
}
设置storyboard里cell的背景颜色:
运行结果:
如同自定义cell,上面的代码中返回cell的时候返回我们自己创建的cell就好了。对于Header/Footer也一样,我们首先要分别实现两个代理方法:一个返回高度,一个返回内容(UIView类型)。
// Header高度
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 100;
}
// Footer高度
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 30;
}
// 自定义Header
- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, TABLE_WIDTH, 100)];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:view.bounds];
imageView.image = [UIImage imageNamed:@"mas091"];
[view addSubview:imageView];
return view;
}
// 自定义Footer
- (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, TABLE_WIDTH, 30)];
view.backgroundColor = [UIColor blackColor];
return view;
}
效果图: