UITableViewCell 是 UITableView 的一个小单元,使用非常频繁,原来我在 iOS学习之UITableView(一): 新手篇创建tableView 中也讲解过 UITableViewCell 的使用,但是还有一些东西没有讲到
Frame
常用使用方法
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *const cellId = @"cellId";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:style reuseIdentifier:cellId];
}
cell.imageView.image =
cell.textLabel.text =
cell.detailTextLabel.text =
return cell;
}
大部分时候官方的 cell 满足不了我们的需求,比如需要调整 imageView的大小,或是调整 textLabel 的高度,如果在上面的方法中直接添加代码
cell.imageView.frame =
cell.textLabel.frame =
cell.detailLabel.frame =
其实是不起作用的。 正确的做法是重载 cell 的 layoutSubviews 方法
@interface DemoCell : UITableViewCell
@end
@implementation DemoCell
-(void)layoutSubviews{
[super layoutSubviews];
self.imageView.frame =
self.textLabel.frame =
self.detailLabel.frame =
}
@end
其实为什么不起作用呢,在 cellForRow 方法中你所写的代码只是告诉 tableView 它应该在对应的 indexPath 去渲染哪个 Cell,在这个方法里面,cell 是一个 readonly 的状态,而你可以在 UITableViewCell.h 中看到
imageView textLabel detailTextLabel contentView
这几个 UI 都是 readonly,所以你在 cellForRow 中去修改 frame 是行不通的,但是在它自身的类方法中去操作就可以,所以只需要通过 layoutSubviews 去改变其 frame 即可
复用问题
tableView 保证性能的就是 cell 的复用,前面我们的用法没毛病,但是如果情况比较多的时候就一定要考虑复用状态清空的问题
其实就是在你使用 cell 之前一定要保证 cell 上的状态时 clean 的,通过 dequeueReusableCellWithIdentifier: 从复用队列中拿到的 cell 就会显示别的行设置过的内容
比如你只有某一行需要显示 detailTextLabel,别的行都不需要显示,于是写了这样的代码
if (indexPath.row == xx) {
cell.detailTextLabel.text = @"****";
}
cell.textLabel.text = @"hello";
如果你的行数没有超过屏幕,这段代码不会有任何问题,但是如果你的行数超过了20,而显示 detail 的行数是第一行,滚动 table 试试,你的 @"****" 可能会出现在任意一行
这里的错误在于,你需要在 xx 行设置 detailTextLabel,那么其他行不应该显示 detail,那么你的代码应该是这样的
if (indexPath.row == xx) {
cell.detailTextLabel.text = @"****";
}
else{
cell.detailTextLabel.text = nil;
}
cell.textLabel.text = @"hello";
这里只是举个例子,千万记住,不管你为某一行或是某些行设置了什么特殊内容,在不需要显示的行千万不要放任不管,一定要置空 所以最高枕无忧的做法是在为 cell 上的 UI 做任何操作之前,先将状态 清零
cell.textLabel.text = nil;
cell.detailTextLabel.text = nil;
if (indexPath.row == xx) {
cell.detailTextLabel.text = @"****";
}
cell.textLabel.text = @"hello";
如果你自定义了子类,那么请重载 prepareForReuse 方法
-(void)prepareForReuse{
[super prepareForReuse];
self.imageView.image = nil;
self.textLabel.text = nil;
self.detailTextLabel.text = nil;
// 其他自定义 UI 操作该 remove remove,该置空置空
}
accessoryView 和 accessoryType
accessoryType 其实就是几种特殊的 accessoryView,帮你定义好了几种可选样式,其实右边的视图可以自行替换,设置 accessoryView 即可,下面这种样式其实也很常见
cell.accessoryView = xxxView;
就是同样要注意前面的复用问题,不需要的行要设置 accessoryView = nil; view 优先级高于 type,如果在某些行要设置 accessoryView,某些行要是设置 accessoryType,在设置 type 之前需要将 view 置为 nil,否则设置可能会无效
点击事件 前面的 switch 事件自己需要实现,而如果是 tap 事件可以通过下面的代理方法来截获
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath;
editingAccessoryType 和 editingAccessoryView 用法类似,只是是 editing 状态时才会显示
cell 的线
大家都知道,iOS 7以后,cell 的线不再是封闭式的,而是前面留出了15个像素的留白,其实这个留白是可以自行控制的,当然如果你想要封闭也是可以设置的
相关属性
// UIView
@property (nonatomic) UIEdgeInsets layoutMargins NS_AVAILABLE_IOS(8_0);
// UITableViewCell
@property (nonatomic) UIEdgeInsets separatorInset
设置 tableView 的 layoutMargins 并设置 cell 的 layoutMargins 和 separatorInset,这三个值应该为同一个值
UIEdgeInsets UIEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right);
主要是 left 和 right 的值,如果为正值,则留白;如果为0 ,则闭合
UIEdgeInsets insets = UIEdgeInsetsMake(0, 5, 0, 0);
闭合则
UIEdgeInsets insets = UIEdgeInsetsZero;
self.tableView.layoutMargins = insets;
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
cell.layoutMargins = insets;
cell.separatorInset = insets;
}
cell 的状态
有时候需要设置 cell 的选中状态,我们之间在子类中重载 setSelected: 方法即可,在 cellForRow 方法中去判断然后去改变 cell 的状态时不可取的,代码可读性非常差 比如选中时打勾,而正常空白状态
if (xx) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else{
cell.accessoryType = UITableViewCellAccessoryNone;
}
在 cell 状态 需要改变时(比如是点击了某行,或是收到了后台的通知需要改变时)再又去改变 cell 的状态,或是去 reloadTable,实现起来貌似没有什么问题,但是代码可读性差,后面排查问题时也比较麻烦
-(void)setSelected:(BOOL)selected{
[super setSelected:selected];
if (selected) {
self.accessoryType = UITableViewCellAccessoryCheckmark;
}
else{
self.accessoryType = UITableViewCellAccessoryNone;
}
}
然后将前面的代码块改为
cell.selected = xx;
当需要改变 cell 状态时,也直接操作其 selected 属性即可 同理,可以操作 setEditing 方法来改变编辑时的属性
当然,也可以自己添加属性来操作
需要改变 cell 的布局,但是又不能 selected 属性,selected 属性被占用,那也可以自定义一个类似的属性,同样在 set 方法中做出对应的操作即可
原则就是 cellForRow 中不应该直接操作 cell 的 UI,通过外部接口,改变其抛出的属性来达到我们的目的,在子类中完成
AttributedText
attributedText 其实很强大,如果说前面的方法还不能够让 cell 足够简单,那么 AttributedText 一定可以满足你的需求,比如在 title 后面需要跟上 icon,这种情况又需要计算 title 的长度,再去渲染一个图片或是 UI,或是需要显示不同的字体或颜色,不用再一堆 if else,本文讲解 cell 的使用, NSAttributedString 的 使用不再这里讲解,大家可以自己尝试,在后面跟上 image,或是 view 都可以
一个粗糙的 demo
@interface DemoCell : UITableViewCell
-(void)cellWithDictioanry:(NSDictionary *)dictionary;
-(void)cellWithModel:(DemoCellModel *)model;
@end
@implementation DemoCell
-(void)prepareForReuse{
[super prepareForReuse];
self.imageView.image = nil;
self.textLabel.text = nil;
self.textLabel.attributedText = nil;
self.detailTextLabel.text = nil;
}
-(void)displayWithDictionary:(NSDictionary *)dictionary{
self.textLabel.attributedText = ;
self.detailTextLabel.text = ;
self.imageView.image =
self.selected = (xxxx);// xxxx 为判断条件
}
-(void)setSelected:(BOOL)selected{
[super setSelected:selected];
if (selected) {
self.accessoryType = UITableViewCellAccessoryCheckmark;
// other changes;
}
else{
self.accessoryType = UITableViewCellAccessoryNone;
}
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *const cellId = @"cellId";
DemoCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[DemoCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellId];
}
[cell displayWithDictionary:dataArray[indexPath.row]];
return cell;
}
本文只讲解cell 的几个简单的小技巧,关于 cell、 TableView 及 tableViewController的详细实现, 可以参考 整洁的 Table View 代码
结语
其实大部分时候,Apple 提供的 cell 足够满足要求了,只是不方便直接操作,需要定义子类去补充一些属性和方法,在自定义cell 时并不需要每次都自己去写新的 label的 UI,大部分通过 label的 AttributedText,Apple 提供的样式,accessoryView,如果还有需要添加的 UI,添加即可,在 layoutSubviews 中定制 UI,不用全部自定义,熟练使用复用机制,不管多复杂的 cell 都可以简单的实现
自定义的 Cell 逻辑是否清楚,直接关系到 tableView 的逻辑是否清楚,不要让你的 TableView 又臭又长