collection view(UICollectionView对象)使用灵活和可扩展的布局来描述有序的数据项,其一般情况下以网格的形式来展示内容,但并非一定如此。
1 基础
为了将数据展示在屏幕中,Collection View需要搭配其它多种对象,其中有些是用户可选对象,而有些则是必须使用类型。
1.1 配合对象
collection views的设计思想与table view的设计思想类似,都是将数据与展示分开,并且也涉及data source和delegate等多种类型,如表 11所示,涉及的每个类,及其负责的功能。
表 11 The classes and protocols for implementing collection views
Purpose |
Classes/Protocols |
Description |
顶层容器和管理 |
UICollectionView UICollectionViewController |
UICollectionView对象定义了可视化区域,该类继承UIScrollView; UICollectionViewController对象负责管理collection view对象,其继承UIViewController类。 |
内容管理 |
UICollectionViewDataSource(protocol) UICollectionViewDelegate(protocol) |
Data source对象是collection view最重要的对象,其管理和创建显示的内容。 Delegate对象提供了用户与collection view对象交换的方式。 |
展示 |
UICollectionReusableView UICollectionViewCell |
所有在collection view中展示的view对象都必须是UICollectionReusableView实例化对象,这个类提供了一种循环使用的机制。 UICollectionViewCell对象是一种循环使用的view,其是主要的使用对象。 |
布局 |
UICollectionViewLayout
UICollectionViewLayoutAttributes
UICollectionViewUpdateItem |
UICollectionViewLayout对象负责管理cell和view的位置、大小和可视化属性。 在collection view布局执行区间,布局对象(layout object)创建了布局属性(UICollectionViewLayoutAttributes对象),从而告诉cell的布局信息。 不管数据项什么时候被插入、删除和移动,布局对象(layout object)都会接收到UICollectionViewUpdateItem对象。用户从来都不需要手动创建该对象。 |
流布局 |
UICollectionViewFlowLayout UICollectionViewDelegateFlowLayout |
UICollectionViewFlowLayout是一种实体布局类,用户使用该类对象来实现网格布局或流式布局。 |
如图 11所示展示了collection view相关对象之间的协作关系,collection view对象从data source对象中获得显示的cell对象;layout 对象使用layout attribute对象来管理cell对象的位置,并将这些layout attribute对象发送给collection view对象;最终collection view对象合并layout 信息和cell信息,并在视图中创建可视化内容。
图 11 Merging content and layout to create the final presentation
1.2 Reusable Views
Collection view使用view的循环利用程序来改善性能。当view对象离开屏幕时,则将其移入reuse queue,而不是将其删除;当有新的内容需要被展示在屏幕时,那么可以使用reuse queue中的view对象,只是数据不同而已。为了促进循环使用,所有在collection view中显示的view类都必须继承UICollectionReusableView类。
Collection view支持如下三种可循环使用类型,每种类型都有明确的用处和用法:
1) Cells
该类型为collection view的主要显示内容,其工作是描述简单项的内容。每个cell对象都是UICollectionViewCell类的实例化对象。
2) Supplementary views
该类型为展示collection view的section信息。与cell对象类似,supplementary view对象也是数据驱动类型,不同的是supplementary view不是强制的,其使用和布置都是由layout object管理。
3) Decoration views
该类型为一种可视化装饰类型,而且它不是由data source管理的数据驱动类型,而是完全由layout object管理。
1.3 Layout Object
layout object完全负责可视化组件的位置和样式,虽然data source提供显示的view对象,但是layout object负责管理view对象的位置、尺寸和显示外观。这种分开独立的责任使得在不修改view对象的情况下,可动态改变view对象的布局。
注意不要将collection view的布局管理与app的子view的布局管理相混淆。collection view的布局管理不需要直接管理这些view对象,相反,layout object创建一些布局信息来描述cells、supplementary views,、和 decoration views对象的位置、尺寸和可视化外观,使得collection view应用这些信息来构建这些view对象的布局。
2 Data Source 与Delegate
与table view类似,collection view也需要data source对象和delegate对象。
-
Data source(必选):其是collection view展示的对象提供者。
-
Delegate(可选):其提供collection view与用户(开发工程师)进行信息交换的方式。
2.1 管理内容
Data source对象负责管理collection view的内容,其中data source对象所属的类必须遵守UICollectionViewDataSource协议。Data source必须向collection view对象提供如下的信息:
-
collection view包含多少项section;
-
collection view每项section又包含了多少个item(cell);
-
每项section和每个item展示什么内容。
Collection view使用多层深度NSIndexPath对象来定位数据项。对于item对象,NSIndexPath对象仅包含两层深度的内容,即一个section数和一个item数;但对于supplementary 和decoration view对象,NSIndexPath对象则可能包含更多层的内容,主要依赖app是如何布局和设计。
NSIndexPath对象是由layout object创建和提供,即section和item的可视化信息是由layout object决定,不同的布局信息展示的section和item信息是完全不同的。如图 21所示,flow layout object展示的section对象是在垂直方向上连续布局,而custom layout提供的section则是非连续的布局安排。
图 21 Sections arranged according to the arrangement of layout objects
2.1.1 数据模型
Apple官方建议采用二维的section和item来组织底层的数据模型,采用这种方式来组织能够更快的访问数据。如图 22所示,底层数组中包含多个子数组,每个数组描述一个section对象的内容,而每个section数组又包含多个item元素。
图 22 Arranging data objects using nested arrays
2.1.2 模型数量
collection view会不断向data source询问有多少项section和多少个item,当如下事件发生时,collection view对象就会询问data source这些信息:
a) collection view第一次被展示时;
b) 修改collection view对象的data source对象时;
c) 用户精确的调用collection view对象的reloadData方法时;
d) collection view delegate对象执行performBatchUpdates:completion:方法时,或者是其执行的move、 insert或 delete 方法。
为了回答collection view这些信息,所以data source对象需要实现UICollectionViewdataSource协议两个方法:
1) numberOfSectionsInCollectionView:方法
该方法返回collection view中有多少项section对象。该方法为可选类型,若未实现该方法,则默认返回为1。
2) collectionView:numberOfItemsInSection:方法
该方法返回每项section有多少个item,并且该方法为必选类型。
如下所示的实现,_data为预先定义的二维数组:
2 // _data is a class member variable that contains one array per section.
3 return [_data count];
4 }
5
6 - (NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section {
7 NSArray* sectionArray = [_data objectAtIndex:section];
8 return [sectionArray count];
9 }
2.2 Cells和Supplementary Views配置
Data source对象的另一个重要任务是提供collection view具体显示的内容:cell和supplementary view对象。Collection view不会遍历这些内容,只是向layout object查询layout attribute信息,然后将布局信息应用于显示内容。为了向collection view提供这些cell和supplementary view对象,需要用户(开发工程师)实现如下内容:
1) 在storyboard文件中,必须嵌入cell或view模版;同时可以选择为每个cell或supplementary view注册(关联)一个controller类。
2) 在data source中,配置reuse queue并配置合适的cell和supplementary view。
为了尽可能高效地使用cell和supplementary view对象,每个collection view都维护一个内置的cell和supplementary view队列(reuse queue)。即当需要显示这些cell和view对象时,不需要进行创建,从而节省的时间和硬件性能。
2.2.1 注册
为了配置和注册cell和supplementary view对象,有两种方式:program和storyboard。
1) storyboard方式
这种方式使用非常简单,只需从库中拖拽item到collection view,并配置相关的属性,其实就是创建collection view与cell(或supplementary view)直接关系。
-
若是cell对象:从库中拖拽一个collection view cell控件到collection view视图中,然后创建定制的class,并将此class关联到cell控件中,同时为cell控件设置reusable view identifier值。
-
若是supplementary view对象:从库中拖拽一个Collection Reusable View控件到collection view视图中,然后创建定制的class,并将此class关联到该控件中,同时为该控件设置reusable view identifier值。
2) program方式
这种方式是使用UICollectionView对象的不同方法来注册cell和supplementary view对象。
-
若是cell对象
- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier - (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier |
参数语义: cellClass:为cell控件关联的类class属性; identifier:为在以后要重复使用的标识符; nib:为包含cell对象的nib对象。 |
-
若是supplementary view对象
- (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier - (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier |
参数语义: viewClass:为view控件关联的类class属性; elementKind:layout object定义的标识符; identifier:为在以后要重复使用的标识符; nib:为包含view对象的nib对象。 kind: |
注册cell和supplementary view对象,必须在进行出队之前进行;并且一旦注册之后,即可重复使用cell和supplementary view对象,而无需在重复注册。Apple不推荐在出队一个或多个对象之后,再修改注册信息。
2.2.2 出队和配置
当Collection view需要显示内容时,它就会向Data source 对象请求cell和supplementary view对象。这里的请求其实是调用UICollectionViewDataSource协议的两个方法:
cellForItemAtIndexPath:(NSIndexPath *)indexPath
-(__kindof UICollectionReusableView*)dequeueReusableSupplementaryViewOfKind:(NSString*)elementKind
withReuseIdentifier:(NSString*)identifier
forIndexPath:(NSIndexPath *)indexPath
其中第一个方法是返回cell对象,用户必须实现该方法;而第一个方法返回supplementary view对象,其是可选方法,具体依赖布局的类型。但两个方法内部都可以按如下操作:
1) 从如下两个方法出队cell对象或supplementary view对象:
-
dequeueReusableCellWithReuseIdentifier:forIndexPath:
-
dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:
2) 使用index path对象配置数据信息;
3) 返回cell或view对象。
Reuse queue会自动从storyboard或nib中创建这些cell和supplementary view对象,并且调用其initWithFrame:方法进行初始化,所以用户可以在关联的class中实现该方法。在创建显示内容后,即可对其进行配置,如下所示:
2 cellForItemAtIndexPath:(NSIndexPath *)indexPath {
3 MyCustomCell* newCell = [self.collectionView dequeueReusableCellWithReuseIdentifier:MyCellID
4 forIndexPath:indexPath];
5
6 newCell.cellLabel.text = [NSString stringWithFormat:@"Section:%d, Item:%d", indexPath.section, indexPath.item];
7 return newCell;
8 }
注意:
若返回的cell和supplementary view对象为nil,或者是其它原因不能显示,那么将导致一个assert错误并中断app。
2.3 section和item编辑
为了插入、删除或移动section对象和item对象,需要按如下步骤操作:
-
更新data source的数据模型;
-
调用UICollectionView对象的插入、删除或移动的合适方法。
2.3.1 简单编辑
与UITableView类似,UICollectionView也提供了一些方法来编辑单一的某一项section,或一个item。如下所示当用户点击collection view的一个item时,就将其删除:
2 {
3 [_array[indexPath.section] removeObjectAtIndex:indexPath.item];
4 NSArray * indexs = [NSArray arrayWithObjects:[NSIndexPath indexPathForItem:indexPath.item inSection:indexPath.section],
5 nil];
6 [collectionView deleteItemsAtIndexPaths:indexs];
7 }
除了上述的删除操作外,还有插入和交换等操作方法,具体内容可参考UICollectionViewDelegate。
2.3.2 批量编辑
与UITableView类的批量操作不同,UICollectionView类提供performBatchUpdates:completion:方法来完成(而不是放在两个方法之间),将UICollectionView的编辑方法都放在如下方法的block中。
- (void)performBatchUpdates:(void (^)(void))updates completion:(void (^)(BOOL finished))completion |
参数语义: updates:为更新的block,对collection view的section或item编辑都放在该block中; completion:为完成后的操作,可以为nil。 |
如下所示,当用户点击collection view的某一项时,将其删除并插入一项新内容,即替换新项:
2 {
3 [_array[indexPath.section] removeObjectAtIndex:indexPath.item];
4 [_array[indexPath.section] insertObject:@"hlw" atIndex:indexPath.item];
5 NSArray * indexs = [NSArray arrayWithObjects:[NSIndexPath indexPathForItem:indexPath.item inSection:indexPath.section],
6 nil];
7 [collectionView performBatchUpdates:^{
8 [collectionView deleteItemsAtIndexPaths:indexs];
9 [collectionView insertItemsAtIndexPaths:indexs];
10 } completion:nil];
11 }
2.4 Selection与Highlight
2.4.1 状态
1) 基本概念
collection view支持对cell进行多种操作:单选、多选和不可选。Collection view会自动探测到对cell的操作。可以将collection view cell有两种特殊状态:
-
selection:该状态是cell的一种长期的状态,是指cell被选择过;
-
highlight:该状态是cell的一种短暂状态,是指cell目前被强调。
2) 背景视图
Collection view会修改cell的属性来指明其cell是selection状态或是hightlight状态,并且UICollectionViewCell对象有一个selectedBackgroundView属性(为UIView类型),当cell对象为selection或hightlight状态时,那么cell将显示selectedBackgroundView属性的背景视图。
2 {
3 contentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
4 cell.myLabel.text =[NSString stringWithFormat:@"S%ld: %@",indexPath.section,_array[indexPath.section][indexPath.item]];
5
6 UIView* backgroundView = [[UIView alloc] initWithFrame:cell.bounds];
7 backgroundView.backgroundColor = [UIColor redColor];
8 cell.backgroundView = backgroundView; //一般情况下显示的背景
9
10 UIView* selectedBGView = [[UIView alloc] initWithFrame:cell.bounds];
11 selectedBGView.backgroundColor = [UIColor whiteColor];
12 cell.selectedBackgroundView = selectedBGView; //当被选中过,或处于强调,则显示该背景
13 return cell;
14 }
影 21 效果图
3) 状态区别
selection和hightlight两种状态之间存在细微的区别,两者分别由UICollectionViewCell类的selected属性以及highlighted属性来标识。并且当用户触碰cell对象时,其状态的变化也不一样,如图 23所示,当点击cell过程中selected和highlighted的变化:
-
当手指按下cell对象时,hightlighted为YES,而selected为NO;
-
当手指抬起时,hightlighted为NO,而selected为NO;
-
当最后结束点击时,hightlighted为NO,而selected为YES。
图 23 Tracking touches in a cell
2.4.2 响应方法
根据cell的两种状态变化,collection view delegate提供如下的方法来响应cell状态的变化,用户可以根据需要实现这些方法或之一:
collectionView:shouldSelectItemAtIndexPath: collectionView:shouldDeselectItemAtIndexPath: collectionView:didSelectItemAtIndexPath: collectionView:didDeselectItemAtIndexPath: collectionView:shouldHighlightItemAtIndexPath: collectionView:didHighlightItemAtIndexPath: collectionView:didUnhighlightItemAtIndexPath: |
2.5 Edit Menu
当长按cell对象时,会出现一个上下文菜单,有3个菜单项:cut、copy和paste。但要显示这个上下文菜单,必须实现UICollectionViewDelegate的3个方法:
1) collectionView:shouldShowMenuForItemAtIndexPath:方法
该方法必须返回YES,指明要上下文菜单。
2) collectionView:canPerformAction:forItemAtIndexPath:withSender:方法
该方法也必须返回YES,然后立即会出现一个有3项的菜单。
3) collectionView:performAction:forItemAtIndexPath:withSender:方法
当用户选择3个菜单项之一,则会执行该方法。
如下所示,当用户点击某一项菜单项,则输出相应的名字:
2 {
3 return YES;
4 }
5 -(BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
6 {
7 return YES;
8 }
9
10 -(void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
11 {
12 NSLog(@"%@",NSStringFromSelector(action));
13 }
影 22 效果图
5 参考文献
[1] Collection View Programming Guide for IOS.