看TableView的资料其实已经蛮久了,一直想写点儿东西,却老是由于各类缘由拖延,今天晚上有时间静下心来记录一些最近学习的TableView的知识。下面进入正题,UITableView堪称UIKit里面最复杂的一个控件了,使用起来不算难,可是要用好并不容易。当使用的时候咱们必需要考虑到后台数据的设计,tableViewCell的设计和重用以及tableView的效率等问题。 数组
下面分9个方面进行介绍: app
1、UITableView概述 框架
UITableView继承自UIScrollView,能够表现为Plain和Grouped两种风格,分别以下图所示: 函数
其中左边的是Plain风格的,右边的是Grouped风格,这个区别仍是很明显的。 布局
查看UITableView的帮助文档咱们会注意到UITableView有两个Delegate分别为:dataSource和delegate。 性能
dataSource是UITableViewDataSource类型,主要为UITableView提供显示用的数据(UITableViewCell),指定UITableViewCell支持的编辑操做类型(insert,delete和reordering),并根据用户的操做进行相应的数据更新操做,若是数据没有更具操做进行正确的更新,可能会致使显示异常,甚至crush。 学习
delegate是UITableViewDelegate类型,主要提供一些可选的方法,用来控制tableView的选择、指定section的头和尾的显示以及协助完成cell的删除和排序等功能。 ui
提到UITableView,就必须的说一说NSIndexPath。UITableView声明了一个NSIndexPath的类别,主要用来标识当前cell的在tableView中的位置,该类别有section和row两个属性,前者标识当前cell处于第几个section中,后者表明在该section中的第几行。 this
UITableView只能有一列数据(cell),且只支持纵向滑动,当建立好的tablView第一次显示的时候,咱们须要调用其reloadData方法,强制刷新一次,从而使tableView的数据更新到最新状态。 spa
2、UITableViewController简介
UITableViewController是系统提供的一个便利类,主要是为了方便咱们使用UITableView,该类生成的时候就将自身设置成了其包含的tableView的dataSource和delegate,并建立了不少代理函数的框架,为咱们大大的节省了时间,咱们能够经过其tableView属性获取该controller内部维护的tableView对象。默认状况下使用UITableViewController建立的tableView是充满全屏的,若是须要用到tableView是不充满全屏的话,咱们应该使用UIViewController本身建立和维护tableView。
UITableViewController提供一个初始化函数initWithStyle:,根据须要咱们能够建立Plain或者Grouped类型的tableView,当咱们使用其从UIViewController继承来的init初始化函数的时候,默认将会咱们建立一个Plain类型的tableView。
UITableViewController默认的会在viewWillAppear的时候,清空全部选中cell,咱们能够经过设置self.clearsSelectionOnViewWillAppear = NO,来禁用该功能,并在viewDidAppear中调用UIScrollView的flashScrollIndicators方法让滚动条闪动一次,从而提示用户该控件是能够滑动的。
3、UITableViewCell介绍
UITableView中显示的每个单元都是一个UITableViewCell对象,看文档的话咱们会发现其初始化函数initWithStyle:reuseIdentifier:比较特别,跟咱们平时看到的UIView的初始化函数不一样。这个主要是为了效率考虑,由于在tableView快速滑动的滑动的过程当中,频繁的alloc对象是比较费时的,因而引入了cell的重用机制,这个也是咱们在dataSource中要重点注意的地方,用好重用机制会让咱们的tableView滑动起来更加流畅。
咱们能够经过cell的selectionStyle属性指定cell选中时的显示风格,以及经过accessoryType来指定cell右边的显示的内容,或者直接指定accessoryView来定制右边显示的view。
系统提供的UITableView也包含了四种风格的布局,分别是:
typedef enum { UITableViewCellStyleDefault, UITableViewCellStyleValue1, UITableViewCellStyleValue2, UITableViewCellStyleSubtitle } UITableViewCellStyle;
这几种文档中都有详细描述,这儿就不在累赘。然而能够想象系统提供的只是最经常使用的几种类型,当系统提供的风格不符合咱们须要的时候,咱们就须要对cell进行定制了,有如下两种定制方式可选:
1、直接向cell的contentView上面添加subView
这是比较简单的一种的,根据布局须要咱们能够在不一样的位置添加subView。可是此处须要注意:全部添加的subView都最好设置为不透明的,由于若是subView是半透明的话,view图层的叠加将会花费必定的时间,这会严重影响到效率。同时若是每一个cell上面添加的subView个数过多的话(一般超过3,4个),效率也会受到比较大的影响。
下面咱们看一个例子:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSArray *sections = [SvTableViewDataModal sections]; SvSectionModal *sectionModal = [sections objectAtIndex:indexPath.section]; static NSString *reuseIdetify = @"SvTableViewCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdetify]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdetify]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.showsReorderControl = YES; for (int i = 0; i < 6; ++i) { UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100 + 15 * i, 0, 30, 20)]; label.backgroundColor = [UIColor redColor]; label.text = [NSString stringWithFormat:@"%d", i]; [cell.contentView addSubview:label]; [label release]; } } cell.textLabel.backgroundColor = [UIColor clearColor]; cell.textLabel.text = [sectionModal.cityNames objectAtIndex:indexPath.row]; return cell; }
在上面这个例子中,我往每一个cell中添加了6个subView,并且每一个subView都是半透明(UIView默认是半透明的),这个时候滑动起来明显就能够感受到有点颤抖,不是很流畅。当把每个subView的opaque属性设置成YES的时候,滑动会比以前流畅一些,不过仍是有点儿卡。
2、从UITableViewCell派生一个类
经过从UITableViewCell中派生一个类,能够更深度的定制一个cell,能够指定cell在进入edit模式的时候如何相应等等。最简单的实现方式就是将全部要绘制的内容放到一个定制的subView中,而且重载该subView的drawRect方法直接把要显示的内容绘制出来(这样能够避免subView过多致使的性能瓶颈),最后再将该subView添加到cell派生类中的contentView中便可。可是这样定制的cell须要注意在数据改变的时候,经过手动调用该subView的setNeedDisplay方法来刷新界面,这个例子能够在苹果的帮助文档中的TableViewSuite工程中找到,这儿就不举例了。
观看这两种定制cell的方法,咱们会发现subView都是添加在cell的contentView上面的,而不是直接加到cell上面,这样写也是有缘由的。下面咱们看一下cell在正常状态下和编辑状态下的构成图:
cell在正常状态下的构成图以下:
进入编辑状态下cell的构成图以下:
经过观察上面两幅图片咱们能够看出来,当cell在进入编辑状态的时候,contentView会自动的缩放来给Editing control腾出位置。这也就是说若是咱们把subView添加到contentView上,若是设置autoresizingMask为更具父view自动缩放的话,cell默认的机制会帮咱们处理进入编辑状态的状况。并且在tableView是Grouped样式的时候,会为cell设置一个背景色,若是咱们直接添加在cell上面的话,就须要本身考虑到这个背景色的显示问题,若是添加到contentView上,则能够经过view的叠加帮助咱们完成该任务。综上,subView最好仍是添加到cell的contentView中。
4、Reordering
为了使UITableVeiew进入edit模式之后,若是该cell支持reordering的话,reordering控件就会临时的把accessaryView覆盖掉。为了显示reordering控件,咱们必须将cell的showsReorderControl属性设置成YES,同时实现dataSource中的tableView:moveRowAtIndexPath:toIndexPath:方法。咱们还能够同时经过实现dataSource中的tableView:canMoveRowAtIndexPath:返回NO,来禁用某一些cell的reordering功能。
下面看苹果官方的一个reordering流程图:
上图中当tableView进入到edit模式的时候,tableView会去对当前可见的cell逐个调用dataSource的tableView:canMoveRowAtIndexPath:方法(此处官方给出的流程图有点儿问题),决定当前cell是否显示reoedering控件,当开始进入拖动cell进行拖动的时候,每滑动过一个cell的时候,会去掉用delegate的tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:方法,去判断当前划过的cell位置是否能够被替换,若是不行则给出建议的位置。当用户放手时本次reordering操做结束,调用dataSource中的tableView:moveRowAtIndexPath:toIndexPath:方法更新tableView对应的数据。
此处给个我写demo中的更新数据的小例子:
// if you want show reordering control, you must implement moveRowAtndexPath, or the reordering control will not show // when use reordering end, this method is invoke - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath { // update DataModal NSArray *sections = [SvTableViewDataModal sections]; SvSectionModal *sourceSectionModal = [sections objectAtIndex:sourceIndexPath.section]; NSString *city = [[sourceSectionModal.cityNames objectAtIndex:sourceIndexPath.row] retain]; [sourceSectionModal.cityNames removeObject:city]; [SvTableViewDataModal replaceSectionAtIndex:sourceIndexPath.section withSection:sourceSectionModal]; SvSectionModal *desinationsSectionModal= [[SvTableViewDataModal sections] objectAtIndex:destinationIndexPath.section]; [desinationsSectionModal.cityNames insertObject:city atIndex:destinationIndexPath.row]; [SvTableViewDataModal replaceSectionAtIndex:destinationIndexPath.section withSection:desinationsSectionModal]; [city release]; }
上面代码中首先拿到源cell所处的section,而后从该section对应的数据中移除,而后拿到目标section的数据,而后将源cell的数据添加到目标section中,并更新回数据模型,若是咱们没有正确更新数据模型的话,显示的内容将会出现异常。
5、Delete & Insert
cell的delete和insert操做大部分流程都是同样的,当进入编辑模式的时候具体的显示是delete仍是insert
取决与该cell的editingStyle的值,editStyle的定义以下:
typedef enum { UITableViewCellEditingStyleNone, UITableViewCellEditingStyleDelete, UITableViewCellEditingStyleInsert } UITableViewCellEditingStyle;
当tableView进入编辑模式之后,cell上面显示的delete仍是insert除了跟cell的editStyle有关,还与 tableView的delegate的tableView:editingStyleForRowAtIndexPath:方法的返回值有关(在这里唠叨一句,其实delegate提供了不少改变cell属性的机会,如非必要,仍是不要去实现这些方法,由于执行这些方法也形成必定的开销)。
delete和insert的流程以下苹果官方文档中给出的图所示:
下面是我写的demo中删除和添加部分的代码:
#pragma mark - - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { NSLog(@"commit editStyle: %d", editingStyle); if (editingStyle == UITableViewCellEditingStyleDelete) { NSArray *sections = [SvTableViewDataModal sections]; SvSectionModal *sourceSectionModal = [sections objectAtIndex:indexPath.section]; [sourceSectionModal.cityNames removeObjectAtIndex:indexPath.row]; [SvTableViewDataModal replaceSectionAtIndex:indexPath.section withSection:sourceSectionModal]; [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationRight]; } else { // do something for add it NSArray *sections = [SvTableViewDataModal sections]; SvSectionModal *sourceSectionModal = [sections objectAtIndex:indexPath.section]; [sourceSectionModal.cityNames insertObject:@"new City" atIndex:indexPath.row]; [SvTableViewDataModal replaceSectionAtIndex:indexPath.section withSection:sourceSectionModal]; [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationRight]; } }
代码中首先判断当前操做是delete操做仍是insert操做,相应的更新数据,最后根据状况调用tableView的insertRowsAtIndexPaths:withRowAnimation:或者deleteRowsAtIndexPaths:withRowAnimation:方法,对tableView的视图进行更新。cell的删除和添加操做相对仍是比较简单的。
6、Cell的Select操做
当咱们在tableView中点击一个cell的时候,将会调用tableView的delegate中的tableView:didSelectRowAtIndexPath:方法。
关于tableView的cell的选中,苹果官方有如下几个建议:
1、不要使用selection来代表cell的选择状态,而应该使用accessaryView中的checkMark或者自定义accessaryView来显示选中状态。
2、当选中一个cell的时候,你应该取消前一个cell的选中。
3、若是cell选中的时候,进入下一级viewCOntroller,你应该在该级菜单从navigationStack上弹出的时候,取消该cell的选中。
这块儿再提一点,当一个cell的accessaryType为UITableViewCellAccessoryDisclosureIndicator的时候,点击该accessary区域一般会将消息继续向下传递,即跟点击cell的其余区域同样,将会掉delegate的tableView:didSelectRowAtIndexPath:方法,当时若是accessaryView为 UITableViewCellAccessoryDetailDisclosureButton的时候,点击accessaryView将会调用delegate的 tableView:accessoryButtonTappedForRowWithIndexPath:方法。
7、批量插入,删除,部分更新操做
UITableView提供了一个批量操做的特性,这个功能在一次进行多个row或者scetion的删除,插入,获取更新多个cell内容的时候特别好用。全部的批量操做须要包含在beginUpdates和endUpdates块中,不然会出现异常。
下面请看我demo中的一个批量操做的例子:
- (void)groupEdit:(UIBarButtonItem*)sender { [_tableView beginUpdates]; // first update the data modal [_tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationTop]; [_tableView deleteSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationTop]; [SvTableViewDataModal deleteSectionAtIndex:0]; SvSectionModal *section = [[SvTableViewDataModal sections] objectAtIndex:0]; [section.cityNames insertObject:@"帝都" atIndex:0]; [SvTableViewDataModal replaceSectionAtIndex:0 withSection:section]; [_tableView endUpdates]; }
上面的例子中咱们能够看到先往tableView的第0个section的第0行添加一个cell,而后将第0个section删掉。按照咱们程序中写的顺序,那么新添加进去的“帝都”,将不在会显示,由于包含它的整个section都已经被删除了。
执行程序先后结果以下图:
demo中第0个section是陕西省的城市,第1个section是北京。左边是执行前的截图,右边是执行后的截图,观察发现结果并不像咱们前面推测的那样。那是由于在批量操做时,无论代码中先写的添加操做仍是删除操做,添加操做都会被推出执行,直到这个块中全部的删除操做都执行完之后,才会执行添加操做,这也就是上面苹果官方图片上要表达的意思。
苹果官方文档有一副图能够帮助咱们更好的理解这一点:
原图中操做是:首先删除section 0中的row 1,而后删除section 1,再向section 1中添加一行。执行完批量更新之后就获得右半边的结果。
8、IndexList
当咱们tableView中section有不少,数据量比较大的时候咱们能够引入indexList,来方便完成section的定位,例如系统的通信录程序。咱们能够经过设置tableView的sectionIndexMinimumDisplayRowCount属性来指定当tableView中多少行的时候开始显示IndexList,默认的设置是NSIntegerMax,即默认是不显示indexList的。
为了可以使用indexlist咱们还须要实现dataSource中一下两个方法:
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView; - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index;
第一个方法返回用于显示在indexList中的内容的数组,一般为A,B,C...Z。第二个方法的主要做用是根据用户在indexList中点击的位置,返回相应的section的index值。这个例子能够在苹果官方给出的TableViewSuite中找到,实现起来仍是很简单的。
9、其余
1、分割线
咱们能够经过设置tableView的separatorStyle属性来设置有无分割线以及分割线的风格,其中style定义以下:
typedef enum { UITableViewCellSeparatorStyleNone, UITableViewCellSeparatorStyleSingleLine, UITableViewCellSeparatorStyleSingleLineEtched } UITableViewCellSeparatorStyle;
同时还能够经过tableView的separatorColor属性来设置分割线的颜色。
2、如何提升tableView的性能
a、重用cell
咱们都知道申请内存是须要时间,特别是在一段时间内频繁的申请内存将会形成很大的开销,并且上tebleView中cell大部分状况下布局都是同样的,这个时候咱们能够经过回收重用机制来提升性能。
b、避免content的从新布局
尽可能避免在重用cell时候,对cell的从新布局,通常状况在在建立cell的时候就将cell布局好。
c、使用不透明的subView
在定制cell的时候,将要添加的subView设置成不透明的会大大减小多个view层叠加时渲染所须要的时间。
d、若是方便,直接重载subView的drawRect方法
若是定制cell的过程当中须要多个小的元素的话,最好直接对要显示的多个项目进行绘制,而不是采用添加多个subView。
e、tableView的delegate的方法如非必要,尽可能不要实现
tableView的delegate中的不少函数提供了对cell属性的进一步控制,好比每一个cell的高度,cell是否能够编辑,支持的edit风格等,如非必要最好不要实现这些方法由于快速的调用这些方法也会影响性能。