知识点回顾:
1.TableView
2.UITableViewController
3.两个协议,一个关于数据加载的协议< UITableViewDataSource >,一个关于性外和外观的协议< UITableViewDelegate >
4.对NSIndexPath类的扩展
5.tableView的单元格cell
6.响应点击事件
7.重用机制下布局子视图时需要注意的问题
一、TableView
tableView继承ScrollView。
风格:
tableView的风格有两种。
tableViewCell的预定义风格有四种。
tableViewCell右侧的按钮风格有多种。
二、UITableViewController
已经初始化好了一个UITableView的UIViewController。包括设置代理,给出了几个代理方法。没有其他特别的。
三、两个协议
知道协议的方法都有哪些功能方便选择性地实现它们。tableView的很多功能是两个协议的方法相互配合才可以实现的,因此想要实现某个功能的时候,需要同时查阅两个协议的方法。为了方便查阅,我就将两个协议的方法放在一起写,按功能分类。
两个协议定义的方法的功能可以分为4种:与外观和显示相关的功能、与行(row)的触摸事件相关的功能、与行的编辑相关的功能、与行的上下移动相关的功能。下面就按这四个功能来对协议方法进行分类整理。
1. 与外观和显示相关的功能
(1)可以分别指定cell、headerView、footerView的高度
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
(2)可以分别在cell、headerView、footerView显示前、显示后的对它们进行一些设置。
(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
(void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section
(void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath
(void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section
(void)tableView:(UITableView *)tableView didEndDisplayingFooterView:(UIView *)view forSection:(NSInteger)section
(3)指定每个分区的headerView、FooterView
(nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;
(nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
(4)指定每一行的深度。如下图,图中,每个分区内的行的深度逐行增加
(NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath
(5)指定分区数
(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
(6)指定每个分区的行数
(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
(7)指定每一行对应的CellView,在这里绑定数据
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
(8)指定每个分区默认headerView的title、默认FooterView的title
(nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
(nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
(9)指定要显示在table右侧的快速浏览列表的分区名单(section title list)
(nullable NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView
(10)指定点击快速浏览列表的某个title时,tableView滑动到哪个section
(NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
以上(1)-(4)为< UITableViewDelegate >协议定义的方法,(5)开始为< UITableViewDataSource >协议定义的方法。
2. 与行的触摸事件相关的功能
(1)指定每一行右侧按钮的类型,指定每一行右侧按钮的点击事件处理
(UITableViewCellAccessoryType)tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath
(void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
(2)指定每行被点击之后是否有颜色改变的反馈(也就是highlight强调),指定每行被强调以后的动作,指定每行失去强调以后的动作
(BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath
(void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath
(void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath
(3)指定某一行被选中或取消选中时真正响应的行,效果等同于重定向到别的行
(nullable NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
(nullable NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath
(4)指定用户选中某一行的事件处理,指定用户选中某行又离开后的事件处理
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
(5)指定某一行可以触发的所有方法(UITableViewRowAction)
(nullable NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
(6)指定是否可以弹出菜单(菜单中显示的就是上面指定的方法的title)。如果允许弹出菜单,在用户横扫某一行的时候,菜单就会出现,那么该菜单就覆盖了该行的删除按钮。也就是说,当编辑模式为删除时,用户做出与删除行相同的手势动作,就会弹出该菜单,而不会出现删除按钮。
(BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
以上均为< UITableViewDelegate >协议定义的方法。
3. 与行的编辑相关的功能
(1)指定每一行允许的编辑模式(增、删、多选、不允许编辑)
(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
(2)指定编辑模式为删除的情况下,每个cell的确认删除按钮的title
(nullable NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath
(3)指定每一行被编辑的时候,背景是否呈现凹陷的效果,默认是YES
(BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath
(4)指定每一行将要开始编辑的时候、已经完成编辑的时候要执行的动作(行的编辑状态自动随table的编辑状态改变,不需要手动操作)
(void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath
(void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath
(5)指定每一行里的子视图sender是否能够执行某个动作(比如某一行中含有textField,可以指定该textField中是否可以进行复制等操作,详细见UIResponder的canPerformAction:方法)
(BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender
(6)这个方法不知道干嘛的:每一行执行了某个方法以后需要进行的操作写在这个方法里???
(void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender
(7)指定每一行是否可编辑
(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
(8)用户对tableView的某一行进行了编辑操作之后,需要在这个方法里对数据进行更新,更新完数据,别忘了让tableView插入或移除新的行,或者直接reloadData。
(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
以上(1)-(6)为< UITableViewDelegate >协议定义的方法,(7)开始为< UITableViewDataSource >协议定义的方法。
4. 与行的上下移动相关的功能
(1)指定用户完成行的移动动作以后,该行真正到达的目标位置(不一定就是用户释放该行的位置)
(NSIndexPath *)tableView:(UITableView *)tableView ```
targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
(2)指定每一行是否可移动
(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
(3)用户对tableView的某一行进行上下移动以后,需要在这个方法中对数据进行更新,更新完数据,别忘了让tableView重载数据(reloadData)。
(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
以上(1)为< UITableViewDelegate >协议定义的方法,(2)开始为< UITableViewDataSource >协议定义的方法。
四、对NSIndexPath类的扩展
在NSIndexPath中扩展了两个属性和一个构造方法。
@interface NSIndexPath (UITableView)
+ (instancetype)indexPathForRow:(NSInteger)row inSection:(NSInteger)section;
@property (nonatomic, readonly) NSInteger section;
@property (nonatomic, readonly) NSInteger row;
@end
两个属性,分别是分区section和行row。
构造方法,可以用section和row来构造一个indexPath对象。
五、TableView的单元格Cell
1. 单元格的两种状态
(1)编辑状态下的状态:
(2)非编辑状态下的状态
2. 自定义单元格
(1)方式:通过继承UITableViewCell来实现。
(2)初始化:由于表格的重用机制会自动调用单元格的初始化方法,也就是initWithStyle:reuseIdentifier:
方法,因此要为自定义单元格重写这个方法,在这个方法中对(某些固定的)子视图进行初始化。
(3)子视图的布局:为了适应不同尺寸,以及屏幕的横屏竖屏状态,应该在自定义单元格的layoutSubviews
方法里面对子控件的frame进行设置。别忘了,tableView自身的frame也需要设置(不过是在Controller的layoutSubviews方法里)。单元格的layoutSubviews
方法调用得很频繁,每次单元格进入表格的可视范围内都会调用一次。
(4)数据的绑定:对于需要绑定数据、显示数据的单元格,比较好的做法是将数据封装成一个类。给单元格添加一个这个数据类的属性,然后重写该属性的setter方法,在setter方法中,一次性实现属性的赋值,以及数据与视图的绑定。对于子视图的个数与数据内容相关的情况,子视图的创建也都是在这个setter方法中完成。
另外,为了减少代码被不必要地重复执行,在setter方法中可以加入一条判断语句,判断一下新的对象和旧的对象是否相等,如果不相等才需要进行赋值,以及后面一系列对子视图的操作。
(5)数据类:上面所述的数据类,就是MVC设计模式中的M,Modal。数据类通常只包含呈现表格内容所需的信息。但是,数据的来源(其他的文件、网络等)也许包含了很多其他的信息。这样,当我们已经成功将数据提取到一个字典中,这个字典也会有很多无用的key-value pair,这些key在数据类中是没有的。此时,利用KVC特性,直接拿字典来初始化数据类,程序会因为字典中含有很多对于数据类来说是未定义的key而崩溃。解决这个问题只需要重写一下setValue:forUndefinedKey:方法就可以了。重写这个方法,不仅可以解决上述问题,还可以解决字典中某个key和数据类中对应的属性名不一致的问题。
六、响应点击事件
1. 定义
点击事件,对于表格来说,准确表述应该是表格的某一行被选中的事件(如果不考虑在cell中自己安插的按钮)。
2. 行是否可以被选中
非编辑状态下是否可以被选中,通过tableView的allowsSelection
属性来设定
在编辑状态下是否可以被选中,通过allowsSelectionDuringEditing
属性来设定
3. 响应方法:
(1)行被选中的响应方法
tableView:didSelectRowAtIndexPath:
这是个协议方法,这个方法的实现中,注意先把该行由选中状态变为未被选中状态以后,再去实现其他的响应动作。让某一行回到未选中状态的方法是这个deselectRowAtIndexPath:animated:
(2)其他点击事件的响应方法:
如果用户点击的是系统定义的accessory按钮,那么响应方法是tableView:accessoryButtonTappedForRowWithIndexPath:
如果用户点击的是用户自定义的accessory按钮,那么响应方法就是这个按钮的target和action指定的方法。
4. 用代码替代用户的触摸动作
(1)让tableView滑动到某个位置
scrollToRowAtIndexPath:atScrollPosition:animated:
indexPath指定了滑动之后要看得到某一行
scrollPosition用来指定了让scrollView滑动哪里位置范围,参数类型是个枚举值:
typedef enum {
UITableViewScrollPositionNone,
UITableViewScrollPositionTop,
UITableViewScrollPositionMiddle,
UITableViewScrollPositionBottom
} UITableViewScrollPosition;
(2)让tableView滑动到靠近被选中的行的位置
scrollToNearestSelectedRowAtScrollPosition:animated:
(3)让某一行被选中,并且让scrollView滑动到该行的位置
selectRowAtIndexPath:animated:scrollPosition
七、重用机制
1. 对重用机制的理解
tableView可以使用不同类型的cell来呈现数据。重用机制就是:把需要用到的类型都登记一下,如果当前数据的呈现需要用到某个类型的cell,系统就会调用该cell类的初始化方法initWithStyle:reuseIdentifier:来实例化一个对象,并与这个数据进行绑定,然后呈现在tableView中。当这个cell移出了tableView的可视范围,就会被加入重用池,如果有一个新数据也需要用这种类型的cell来呈现的话,就会从重用池中取出这个cell对象,绑定新的数据,然后呈现在tableView中。这样就实现了cell对象的重用。
2. 重用机制下管理cell的子视图布局时需要注意的问题
需要注意的是,cell对象每次在呈现到tableView上之前都会调用一下其对应类的layoutSubviews方法。在这个方法执行之前,系统已经按照新数据的要求更新了cell的大小(也就是self.frame.size或者self.bounds.size),但是没有更新这个cell的contentView的大小。也就是说,如果这个cell对象是刚刚初始化好,第一次拿来用,那么cell的contentView的大小和cell的大小是一样的。但是如果这个cell是从重用池中拿出来的,那么cell的contentView的大小还是上一次使用的时候的大小。因此,在对子控件的位置进行布局的话,尽量相对于cell的bounds。如果非要相对于contentView的大小来布局子视图,就要记得先执行self.contentView.bounds=self.bounds;来更新一下contentView的大小。