作者两年开发经验,通过通读api重温TableView,并记录
作者:Roger
1. header悬浮顶部
-
UITableViewStylePlain
A plain table view. Any section headers or footers are displayed as inline separators and float when the table view is scrolled.
-
UITableViewStyleGrouped
A table view whose sections present distinct groups of rows. The section headers and footers do not float.
如果需要Section header实现悬浮顶部的效果,需要选择创建UITableViewStylePlain
类型的Cell
,并且实现代理方法
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{ };
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section{ };
如果选择UITableViewStyleGrouped
类型,Header会随着Cell一同上下滑动(以上逻辑同样适用于Footer)。
另外需要知道的一点,如果用户通过
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
方法创建TableView时,必须选择UITableViewStyle
,且之后不能修改。如果通过initWithFrame:
方法创建,默认创建的是UITableViewStylePlain
类型的Cell。
2. separator[分离器]
UITableView拥有四个与separator相关的属性,分别是:
separatorEffect
separatorColor
separatorStyle
separatorInset
separatorEffect
首先来看一下iOS8加入的新属性eparatorEffect
tableview.backgroundView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"detail.jpg"]];
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
UIVibrancyEffect *vibrancyEffect = [UIVibrancyEffect effectForBlurEffect:blurEffect];
tableview.separatorEffect = vibrancyEffect;
为tableView
设置一张背景图,然后通过UIBlurEffect
[蒙层效果]创建一个对象赋给tableView
的separatorEffect
属性,并且将Cell的背景色设置为clearColor
,即可获得非常酷炫的半透明Cell效果了。
separatorInset
其次,有时候我们想将Cell的分割线置顶,会使用separatorInset
属性,separatorInset
只能设置左右边距的与屏幕的距离[上下是不能改变的],并且现在Cell边界线始终与屏幕左侧有一段距离,通过以下方法可以将分割线置顶:
-(void)viewDidLayoutSubviews{
if ([_tableview respondsToSelector:@selector(setSeparatorInset:)]) {
[_tableview setSeparatorInset:UIEdgeInsetsMake(0,0,0,0)];
}
if ([_tableview respondsToSelector:@selector(setLayoutMargins:)]) {
[_tableview setLayoutMargins:UIEdgeInsetsMake(0,0,0,0)];
}
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
if ([cell respondsToSelector:@selector(setSeparatorInset:)]) {
[cell setSeparatorInset:UIEdgeInsetsZero];
}
if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
3. cellLayoutMarginsFollowReadableWidth
在Ipad上,当tableView横屏时,会根据内容留有空白。此参数用于判断是否需要这么做。如下图:
如果设置属性为YES,则不会出现此情况。
4. Creating Table View Cells
Cell拥有两种创建可复用Cell方式,我们将分别介绍两种方式需要注意的地方
-registerNib:forCellReuseIdentifier:
-registerClass:forCellReuseIdentifier:
registerNib:forCellReuseIdentifier:
想利用此方法创建可复用Cell,首先必须是xib构建的cell,其次需要注册Cell
[_tableview registerNib:[UINib nibWithNibName:@"TableViewCell" bundle:nil] forCellReuseIdentifier:@"firstCell"];
最后实现tableview的delegate方法
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
TableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"firstCell"];
return cell;
}
需要注意的地方:
用户可以通过重写-(void)awakeFromNib{}
方法来绘制cell。并且cell如果没有找到复用时,会通过-(instancetype)initWithCoder:(NSCoder *)aDecoder
方法创建新的cell。
registerClass:forCellReuseIdentifier:
如果cell并没有通过xib绘制,可以调用此方法来创建可复用cell。
[_tableview registerClass:[TableViewCell class] forCellReuseIdentifier:@"third"];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
TableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"third"];
return cell;
}
需要注意的地方:
因为Cell不是通过xib绘制的,所以不会调用-(void)awakeFromNib{}
方法,所以如果想要重写cell,可以调用(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
,这一点需要开发者记住。
5. When to use dequeueReusableCellWithIdentifier vs dequeueReusableCellWithIdentifier : forIndexPath
这两个方法看上去没有差别呀,唯一一点的区别就是后者多了一个forIndexPath。仔细阅读api发现,前者是老方法,后者是iOS6提供的新方法。实际操作之后发现,当我们没有通过registerClass
或registerNib
注册cell时,调用这两个方法,前者会返回一个nil,后者会直接崩溃。所以如果在未注册cell的情况下,调用第一种方式创建可复用cell时,需要这么处理:
FirstTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"first"];
if (cell == nil) {
cell = [[FirstTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"first"];
}
return cell;
}
这还没有完,还需要重写cell的- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
方法。需要注意的是,无论上面的代码里调用的是init
,initWithFrame
方法创建cell,最后都会执行到initWithStyle
这个方法里面来。
不过话说回来,如果做到每次使用cell前都注册cell,那么这两个方法就没有差别了。[这样的理解是基于以上的认识得出的,不一定正确,有待验证]
扩展阅读 :
When to use dequeueReusableCellWithIdentifier vs dequeueReusableCellWithIdentifier : forIndexPath
6. Accessing Header && Footer Views
tableHeaderView && tableFooterView
The table header/footer view is different from a section header/footer.
sectionHeaderHeight && sectionFooterHeight
This nonnegative value is used only in section group tables and only if the delegate doesn't implement the tableView:heightForFooterInSection:
method.
headerViewForSection && footerViewForSection in [UITableViewHeaderFooterView]
An index number that identifies a section of the table. Table views in a plain style have a section index of zero.
7. Accessing Cells and Sections
cellForRowAtIndexPath: && - indexPathForCell:
总之记住,可以通过位置获取对应的cell,反之也可以通过cell获取它对应的位置。
- visibleCells: and - indexPathsForVisibleRows:
分别是获取屏幕内的所有cell和所有cell的位置。
8. Estimating Element Heights
estimatedRowHeight && estimatedSectionHeaderHeight && estimatedSectionFooterHeight
设置estimatedRowHeight以减少首次显示的计算量
默认情况下,首次显示之前,系统都会一次性全部计算出所有Cell的高度,这简直不能忍啊!要是有10000行,那岂不是要卡死=。=
所以iOS 7以后,UITableView有了一个新的属性:estimatedRowHeight。
从属性名上就可以看出,这个属性可以为Cell预先指定一个“估计”的高度值,这样的话,系统就可以先按照估计值布局,然后只获取显示范围内的Cell的高度,这样就不会一次性计算全部的了。
iOS7 and later ,使用此方法可以自动布局cell的高度,十分便捷。你只需要加入如下代码:
_tableview.estimatedRowHeight = 44;
_tableview.rowHeight = UITableViewAutomaticDimension;
estimatedRowHeight默认值为0,即不使用预估cell高度功能。另外在iOS8系统中rowHeight的默认值已经设置成了UITableViewAutomaticDimension
,所以第二行代码可以省略。
9. Scrolling the Table View
- scrollToRowAtIndexPath:atScrollPosition:animated:
Invoking this method does not cause the delegate to receive a scrollViewDidScroll: message, as is normal for programmatically invoked user interface operations.
此方法不会触发scrollViewDidScroll:
方法。
调用此方法,让tableView滚动到指定的行数,第二个参数的作用是让此cell显示在屏幕的顶/中/低部。
如果某个cell在屏幕内只展示了一部分,当用户点击这个cell时,我们希望这个cell自动移动使其能展示完整,这时我们只需要调用这个方法并且传入UITableViewScrollPositionNone
这个参数。
- scrollToNearestSelectedRowAtScrollPosition:animated:
让点击的cell显示在屏幕的顶/中/低部。
10. Managing Selections
- indexPathForSelectedRow && indexPathsForSelectedRows
要使用这两个属性,首先需要设置属性allowsSelection = YES && allowsMultipleSelection = YES。类似属性还有allowsSelectionDuringEditing
&& allowsMultipleSelectionDuringEditing
11. Inserting, Deleting, and Moving Rows and Sections
在对cell进行插入,删除,移动和重载时,api为我们提供了一系列的方法:
– insertRowsAtIndexPaths:withRowAnimation:
– deleteRowsAtIndexPaths:withRowAnimation:
– moveRowAtIndexPath:toIndexPath:
– reloadRowsAtIndexPaths:withRowAnimation:
– insertSectionsAtIndexPaths:withRowAnimation:
– deleteSections:withRowAnimation:
– moveSection:toSection:
– reloadSections:withRowAnimation:
当我们在同一时刻(点击某按钮时),对多个cell进行插入,删除,移动和重载时,就需要引入beginUpdates
和endUpdates
方法。
- beginUpdates && - endUpdates
beginUpdates和endUpdates方法是一对绑定在一起的方法,用来对UITableView批量更新操作。先看一下多个插入删除操作不使用beginUpdates
和endUpdates
的情况:
NSMutableArray *array1 = (NSMutableArray *)self.arraySections[0];
//操作1 删除
[array1 removeObjectAtIndex:0];
[self.tableMain deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
//操作2 插入
[array1 insertObject:@"insert" atIndex:3];
[self.tableMain insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:3 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
//操作...
这种做法对于每一个操作,是严格按照顺序执行的,先执行操作1,再执行操作2…再执行任意一个操作的时候,先更新数据源,然后调用表视图的相关操作,该操作方法执行完毕后,会立即调用相关的代理方法更新表视图。更新完毕后继续执行下一个操作…即整个过程是不断地调用代理方法来更新表视图。因此对于这种按序更新的方法,每一个更新操作并不是同时进行的,如果有很多个操作,可能通过肉眼就能看出更新的先后,这通常并不是我们想要的。
通过beginUpdates和endUpdates则可以批量处理操作,区别于上面的操作一个一个执行然后不断更新表视图,批量处理实现了在所有操作执行完后调用代理方法一次性更新表视图,这种方法保证了所有操作最终是同时更新的。
- beginUpdates和endUpdates批量更新有三部曲:
更新数据源(所有操作)
创建相应的indexPaths数组
执行操作
下面举例说明:
//更新数据源
NSMutableArray *array1 = (NSMutableArray *)self.arraySections[0];
[array1 removeObjectAtIndex:0];
[array1 removeObjectAtIndex:2];
[array1 insertObject:@"111" atIndex:1];
[array1 insertObject:@"333" atIndex:3];
//创建相应的indexPaths数组
NSArray *indexPathsDelete = @[[NSIndexPath indexPathForRow:0 inSection:0],[NSIndexPath indexPathForRow:2 inSection:0]];
NSArray *indexPathsInsert = @[[NSIndexPath indexPathForRow:1 inSection:0],[NSIndexPath indexPathForRow:3 inSection:0]];
//执行操作
[self.tableMain beginUpdates];
[self.tableMain deleteRowsAtIndexPaths:indexPathsDelete withRowAnimation:UITableViewRowAnimationFade];
[self.tableMain insertRowsAtIndexPaths:indexPathsInsert withRowAnimation:UITableViewRowAnimationFade];
[self.tableMain endUpdates];
beginUpdates和endUpdates方法对之间的操作执行后并没有立刻更新表视图,而是等endUpdates方法返回后才调用代理方法一次性更新的,因此所有更新动画都是同时执行的。
特别提醒:当删除和插入同时执行时,无论代码中插入操作是否先于删除,实际都是先执行删除再执行插入等操作。
12. Reloading the Table View
- reloadData
It should not be called in the methods that insert or delete rows, especially within an animation block implemented with calls to beginUpdates and endUpdates.
13. Notifications
UITableViewSelectionDidChangeNotification
Posted when the selected row in the posting table view changes.
There is no userInfo dictionary associated with this notification.
没有使用过,但是需要有一个印象。
p.s
以上是作者通过阅读API tableview部分,结合自己的工作经验,列出了作者觉得重要的或以前理解不透彻的知识点。若有错误,请在留言处提出。谢谢。