文档翻译-Collection View - 1.2

作者: 丨n水瓶座菜虫灬 | 来源:发表于2017-06-22 22:20 被阅读112次

    设计你的数据源和委托

    任何集合视图都必须有一个数据源对象。这个数据源对象是你的app显示的内容。它可以是一个来自于你的app的数据模型的对象,或者是管理该集合视图的控制器。数据源唯一的要求是它必须能够提供集合视图所需的信息,例如它有多少个items和当显示这些items时要使用哪些视图。
    委托对象是一个可选的(但推荐)对象,用于管理与内容的呈现和交互相关的方面。虽然委托主要的职责是来管理cell的高亮和选择,但也可以扩展来提供其他信息。例如,流式布局扩展了几本委托行为来自定义布局度量,例如cells的大小和它们之间的间距。

    数据源管理你的内容

    数据源对象的职责是来管理你将要使用集合视图来呈现的内容。数据源对象必须遵守UICollectionViewDataSource协议,该协议定义了你必须支持的基本行为和方法。数据源的工作是来提供集合视图以下问题的答案:

    • 集合视图包含多少个分区?
    • 对于一个给定的分区,该分区包含多少个items?
    • 对于给定的分区和item,应使用什么视图来显示相应的内容?

    分区和items是集合视图内容的基本组织原则。集合视图通常至少有一个分区并且可能有更多的分区。每个分区,包含0个或更多的items。items代表你想要呈现的主要内容,而分区将这些items组织成逻辑组。例如,一个照片app可能会使用分区来代表一张相册或同一天拍摄的一组照片。

    集合视图是指使用NSIndexPath对象所包含的数据。当尝试定位一个item时,集合视图通过布局对象提供索引路径信息给它。对于items, 索引路径包含一个分区编号和一个item编号。对于补充和装饰视图,索引路径包含布局对象提供的所有值。附加到补充和装饰视图上的索引路径的含义取决于你的app,尽管第一个索引对应于数据源中的特定分区。这些视图的索引路径更多的是标识而不是意义,确定当前正在考虑什么样的视图。因此,例如,如果你有补充视图为分区创建页眉和页脚,这在流式布局中可以看到,通过索引路径提供的相关信息是分区引用。

    注意:尽管标准索引路径支持多个级别,但集合视图的cells只支持有”section”和”item”两个级别深度的索引路径,和UITableView类的索引路径很相似。如果需要,补充视图和装饰视图可以有更多复杂的索引路径。其索引路径大于1的元素被解释为对应于路径中第一个索引指定的分区。传统上,只有第二个索引是必要的,但补充和装饰视图不限于两个。在设计数据源时要牢记这一点。

    无论你如何在你的数据对象上安排分区和items, 这些分区和items的可视化呈现仍然由布局对象确定。不同的布局对象可以非常不同地呈现分区和item数据,如图2-1所示。在该图中,流式布局对象垂直地排列各个分区,每个分区都与前一个分区相同。自定义布局对象可以将分区放置在一个非线形布局中,再次示范布局与实际数据的分离。

    图2-1
    设计数据对象

    有效的数据源使用分区和items来帮忙组织其底层数据对象。组织你的数据到分区和items中使其以后更容易实现你的数据源方法。并且由于你的数据源方法会频繁地调用,你希望确保这些方法的实现能够尽可能快地获取数据。
    一个简单的解决方案(但不是唯一的解决方案)是为你的数据源使用一个嵌套数组,如图2-2所示。在这个配置中,顶层数组包含一个或多个数组代表数据源的分区。每一个分区数组包含该分区内的items数据。在一个分区中查找item是一个获取它的分区数组,然后再从该数组中获取item的问题。这种类型的安排便于管理适当大小的items集合,并根据需要检索单个item。

    图2-2

    当设计你的数据结构时,你总是可以从一个简单的数组开始,并根据需要移动到更高效的结构中。一般来说,你的数据对象不应该是性能瓶颈。集合视图通常访问数据源,只计算一共有多少个对象,并获取当前屏幕上元素的视图。如果布局对象仅依赖于数据对象的数据,则当数据源包含数千个对象时,性能可能会受到严重影响。

    告诉集合视图你的内容

    集合视图询问你的数据源的问题是它包含多少个分区和每个分区包含多少个items。当下列动作发生时,集合视图会询问你的数据源来提供这些信息:

    • 集合视图首次显示
    • 给集合视图赋值一个不同的数据源对象
    • 指定调用集合视图的reloadData方法
    • 集合视图使用performBatchUpdates:completion:或任何移动、增加、删除的方法来执行一个block

    你使用numberOfSectionsInCollectionView:方法来提供分区数目,并使用collectionView:numberOfItemsInSection:方法来提供每个分区中的items数目。你必须实现collectionView:numberOfItemsInSection:方法,但是如果你的集合视图仅仅只有一个分区,numberOfSectionsInCollectionView: 方法的实现是可选的。两个方法都返回适当信息的整数值。
    如果你如图2-2所示那样实现数据源,你的数据源方法的实现可能与列表2-1所示一样简单。在这些代码中,_data变量是存储分区的顶层数组的数据源的自定义成员变量。获取该数组的计数将产生该分区的数目。获取其子数组中的计数将产生分区中items的数目。当然,你自己的代码应该做错误检查,以确保返回的值是有效的。

    //Listing 2-1  Providing the section and item counts
    - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView {
        // _data is a class member variable that contains one array per section.
        return [_data count];
    }
    
    - (NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section {
        NSArray* sectionArray = [_data objectAtIndex:section];
        return [sectionArray count];
    }
    
    配置cells和补充视图

    你的数据源另外一个重要的任务是提供集合视图用来显示你的内容的视图。集合视图不会跟踪你的app的内容。它只需要你给出的视图,并将当前的布局信息应用于它们。因此,视图上显示的所有内容都是你的责任。
    在你的数据源上报它管理有多少个分区和items后,集合视图要求布局对象来提供集合视图内容的布局属性。在某些时候,集合视图要求布局对象提供特定矩形中的元素列表(通常是可见的矩形)。集合视图使用该列表询问你的数据源对应的cells和补充视图。为了提供这些cells和补充视图,你的代码必须执行以下操作:

    1. 将你的模版cell和视图嵌入到故事板文件中(或者,为每个支持的cell或视图注册一个类或nib文件)
    2. 在你的数据源中,当被询问时出列并配置相应的cell或视图

    为了确保以最有效的方式使用cell和补充视图,集合视图承担了为你创建这些对象的责任。每个集合视图维护当前未使用的cells和补充视图的内部队列。代替你自己创建对象,简单的询问集合视图来提供你想要的视图。如果有一个在重用队列中正在等待,集合视图会准备好并将其快速地返回给你。如果没有在等待,集合视图会使用注册的类或nib文件来创建一个新的并把它返回给你。因此,每次你取回一个cell或者视图时,总是能得到一个现成的对象。
    重用标识符使注册多种类型的cells和多种类型的补充视图成为可能。重用标识符是一个字符串,用于区分注册cell和视图类型。字符串的内容只与数据源对象有关。但当被请求一个视图或者cell时,你可以使用提供的索引路径来确定你想要的哪种类型的视图或cell,然后通过合适的重用标识符号的获取方法来获取。

    注册你的cells和补充视图

    你可以用代码或者在故事板文件中配置集合视图的cells和视图。
    在故事板中配置cells和视图。当在故事板中配置cells和补充视图时,你可以拖拽item到集合视图并配置它。这会在集合视图和相应的cells或视图之间创建一个关系。

    • 对于cells, 在对象库中拖拽一个CollectionViewCell然后把它放置在集合视图中。将cell的自定义类和集合可重用视图标识符设置为合适的值。
    • 对于补充视图,在对象库中拖拽一个CollectionReusableView,然后把它放置在集合视图中。将视图的自定义类和集合可重用视图标识符设置为合适的值。

    用代码配置cells。使用registerClass:forCellWithReuseIdentifier:方法或registerNib:forCellWithReuseIdentifier:方法来将你的cell与重用标识符关联。你可以称这些方法为父视图控制器初始化过程的一部分。
    用代码配置补充视图。使用registerClass:forSupplementaryViewOfKind:withReuseIdentifier:方法或registerNib:forSupplementaryViewOfKind:withReuseIdentifier:方法来将一种类型的视图与重用标识符相关联。你可以称这些方法为父视图控制器初始化过程的一部分。
    虽然只使用一个重用标识符来注册cells,但补充视图需要一个被称为类型字符串的附加标识符。每个布局对象负责定义它支持的补充视图的种类。例如,UICollectionViewFlowLayout 类支持两种类型的补充视图:分区首视图和分区尾视图。为了标识视图的这两种类型,它定义了字符串常量UICollectionElementKindSectionHeaderUICollectionElementKindSectionFooter。在布局过程中,布局对象包含用于该视图类型的其他布局属性的类型字符串。然后,集合视图将信息传递给数据源。然后,数据源使用类型字符串和可重用标识符来决定哪个视图对象用来出列和返回。

    注意:如果你实现你自己的自定义布局,你负责定义你的布局支持的补充视图的种类。一个布局可以支持多个补充视图,每个补充视图都有它自己的类型字符串。关于自定义布局更多信息,请看Creating Custom Layouts.

    注册是一个一次性事件,必须在你意图出列cells或视图之前发生。在已经注册之后,你可以根据需要出列任意多个cells或视图,而无需重新注册它们。不推荐在出列一个或多个items后改变注册信息。最好是只注册一次你的cells和视图并完成它。

    出列并配置cells和视图

    你的数据源对象负责在集合视图询问它们的时候来提供cells和补充视图。UICollectionViewDataSource协议为这个目的包含了两个方法:collectionView:cellForItemAtIndexPath:collectionView:viewForSupplementaryElementOfKind:atIndexPath:。因为cells是集合视图的必备元素,你的数据源对象必须实现collectionView:cellForItemAtIndexPath: 方法,但是collectionView:viewForSupplementaryElementOfKind:atIndexPath:方法是可选的,这依赖于使用的布局类型。在这两种情况下,你实现这些方法都遵循一个非常简单的模式:

    1. 使用 dequeueReusableCellWithReuseIdentifier:forIndexPath:或者dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:方法出列一个cell或者合适类型的视图。
    2. 使用特定索引路径的数据来配置该视图。
    3. 返回该视图。

    出列过程的目的是减轻你必须创建一个cell或视图的责任。只要你以前注册了一个cell或视图,出列过程保证不会返回nil。如果在重用队列中没有cell或给定类型的视图,则出列方法会使用你的故事板或使用你注册的类或nib文件简单的创建一个。
    从出列过程返回的cell应处于原始状态,并准备配置新数据。对于必须创建的cell或视图,出列过程使用正常的进程创建并初始化它-也就是说,通过从故事板或nib文件中加载视图,或者通过创建一个新的实例并使用initWithFrame:方法进行初始化。相比之下,没有从新创建的数据项,而是从一个重用队列中获取的条目可能已经包含了以前使用的数据。在这种情况下,出列方法会调用该条目的prepareForReuse方法,让它有机会返回原始状态的条目。当实现自定义cell或视图类时,可以覆盖该方法来将属性重置为默认值,并执行任何其他的清除。
    当数据源出列视图后,它将使用新数据来配置视图。可以使用传递给数据源方法的索引路径来定位合适的数据对象,然后将该对象的数据应用于视图。在配置视图之后,从方法中返回它并完成。列表2-2显示一个简单的例子关于如何配置一个cell。在出列该cell后,该方法使用cell的位置信息来设置cell的自定义标签,然后返回这个cell。

    //Listing 2-2  Configuring a custom cell
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
                  cellForItemAtIndexPath:(NSIndexPath *)indexPath {
           MyCustomCell* newCell = [self.collectionView dequeueReusableCellWithReuseIdentifier:MyCellID
                                                                          forIndexPath:indexPath];
    
           newCell.cellLabel.text = [NSString stringWithFormat:@"Section:%d, Item:%d", indexPath.section, indexPath.item];
           return newCell;
    }
    

    注意:当从数据源返回视图时,总是返回一个有效的视图。即使由于某些原因,无法显示所请求的视图而返回nil,会造成断言,并且应用程序会终止,因为布局对象期望通过这个方法返回有效的视图。

    插入,删除和移动分区和items

    要插入,删除或删除一个单独的分区或item,请按照下列步骤操作:

    1. 在数据源对象上更新数据。
    2. 调用集合视图的合适方法来插入或删除该分区或item。

    在通知集合视图的任何更改之前,更新数据源是非常重要的。集合视图方法会假设你的数据源包含当前正确的数据。如果没有,则集合视图可能会从你的数据源接受到错误的items集合,或者询问不存在的items并导致应用程序崩溃。
    当你以编程方式添加,删除或移动单个item时,集合视图的方法会自动创建动画来反应更改。如果你想将多个更改一起动画,你必须完成所有的插入,删除,或移动在一个block内调用,并通过block的performBatchUpdates:completion:方法。批量更新过程随后会同时对所有更改进行动画处理,你可以自由地混合调用来插入,删除或移动items在同一个block中。
    列表2-3显示了如何执行批处理更新来删除当前选定的items的简单示例。该block在执行performBatchUpdates:completion: 方法中首先调用自定义方法来更新数据源。然后告诉集合视图来删除这些items。你提供的更新block和完成block会同步执行。

       //Listing 2-3  Deleting the selected items
     [self.collectionView performBatchUpdates:^{
       NSArray* itemPaths = [self.collectionView indexPathsForSelectedItems];
    
       // Delete the items from the data source.
       [self deleteItemsFromDataSourceAtIndexPaths:itemPaths];
    
       // Now delete the items from the collection view.
       [self.collectionView deleteItemsAtIndexPaths:itemPaths];
    } completion:nil];
    
    管理视觉状态的选中和高亮

    集合视图默认支持单项选择,并且可以配置为可以支持多项选择或完全禁用选择。集合视图检测其边界内的轻击,并高亮或选择相应的cell。在大多数情况下,集合视图仅修改cell的属性来表明其是选中的或高亮的;它不会改变cell的视觉外观,有一个例外。如果cell的selectedBackgroundView属性包含一个有效的视图,集合视图会在cell是高亮或选中时显示该视图。
    列表2-4显示了可以在自定义集合视图cell中实现的代码,以便于突出高亮和选中状态的更改外观。当cell第一次加载的时候和在cell不是高亮或不是选中的时候,cell的backgroundView属性总是一个默认视图。一旦cell是高亮或选中时selectedBackgroundView 属性会替换默认背景视图。在这种情况下,当被选中或高亮时cell的背景色会从红色改变为白色。

       //Listing 2-4  Setting the background views to indicate changed states
    UIView* backgroundView = [[UIView alloc] initWithFrame:self.bounds];
    backgroundView.backgroundColor = [UIColor redColor];
    self.backgroundView = backgroundView;
    
    UIView* selectedBGView = [[UIView alloc] initWithFrame:self.bounds];
    selectedBGView.backgroundColor = [UIColor whiteColor];
    self.selectedBackgroundView = selectedBGView;
    

    集合视图的代理使用以下方法提供集合视图,以便于高亮和选中:

    • collectionView:shouldSelectItemAtIndexPath:
    • collectionView:shouldDeselectItemAtIndexPath:
    • collectionView:didSelectItemAtIndexPath:
    • collectionView:didDeselectItemAtIndexPath:
    • collectionView:shouldHighlightItemAtIndexPath:
    • collectionView:didHighlightItemAtIndexPath:
    • collectionView:didUnhighlightItemAtIndexPath:

    这些方法提供给你许多机会来调整高亮/选中集合视图所需的精确规范行为。
    例如,如果你喜欢自己来绘制一个cell的选中状态,你可以把selectedBackgroundView属性设置为nil,用你的代理对象应用任何视觉变化给该cell。你可以在collectionView:didSelectItemAtIndexPath:方法中应用视觉变化,并在collectionView:didDeselectItemAtIndexPath:方法中移除它们。
    如果你喜欢自己来绘制高亮状态,你可以重写collectionView:didHighlightItemAtIndexPath:collectionView:didUnhighlightItemAtIndexPath:代理方法,并使用它们来运用你的高亮。如果你在selectedBackgroundView属性中也指定了一个视图,你应该对cell的内容视图进行更改,以确保更改是可见的。列表2-5显示了使用内容视图背景色更改高亮显示的简单方法。

    //Listing 2-5  Applying a temporary highlight to a cell
    - (void)collectionView:(UICollectionView *)colView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath {
        UICollectionViewCell* cell = [colView cellForItemAtIndexPath:indexPath];
        cell.contentView.backgroundColor = [UIColor blueColor];
    }
    
    - (void)collectionView:(UICollectionView *)colView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath {
        UICollectionViewCell* cell = [colView cellForItemAtIndexPath:indexPath];
        cell.contentView.backgroundColor = nil;
    }
    

    cell的高亮状态和选中状态之间有细微但重要的区别。高亮状态是一个过渡状态,当用户手指仍在触摸设备时,你可以将可见高亮应用于该cell。这种状态当集合视图正在跟踪cell上的触摸事件时设置为YES,当触摸事件停止时,高亮状态返回值为NO。相比之下,选中状态只在一系列触摸事件结束之后才改变-具体地,当这些事件指示用户尝试选择cell时。
    图2-3阐释当用户触摸一个未选择的cell时产生的一系列步骤。初始按下事件导致集合视图将cell的高亮状态改变为YES,即使做这些并不会自动改变cell的外观。如果最终的抬起事件发生在cell上,高亮状态返回NO并且集合视图将选中状态改变为YES。当用户改变选中状态,集合视图在cell的selectedBackgroundView属性上显示该视图,但这是集合视图对cell唯一的视觉变化。其他视觉上的变化必须通过你的代理对象来完成。

    图2-3

    无论用户是选择还是取消选择cell,cell的选中状态总是由最后一件事来改变。
    cell的点击首先始终会改变cell的高亮状态。只有在点击系列事件之后和应用于任何高亮被移除之后,才会改变cell的选中状态。当设计你的cell时,你应该确保你的高亮和选中状态的视觉外观不会发生意外冲突。

    给cell显示编辑菜单

    当用户在cell上执行长按手势,集合视图意图为cell显示一个编辑菜单。这个编辑菜单可以用来剪切,复制和粘贴cells在集合视图中。在显示编辑菜单之前,必须满足几个条件:

    • 代理必须实现与处理操作相关的三个方法:
      1. collectionView:shouldShowMenuForItemAtIndexPath:
      2. collectionView:canPerformAction:forItemAtIndexPath:withSender:
      3. collectionView:performAction:forItemAtIndexPath:withSender:
    • collectionView:shouldShowMenuForItemAtIndexPath:方法必须为指定的cell返回YES
    • collectionView:canPerformAction:forItemAtIndexPath:withSender:方法必须为至少一个所需的操作返回YES。集合视图支持以下操作:
      cut:
      copy:
      paste:

    如果这些条件都满足了,并且用户从菜单中选择了一个操作,集合视图会调用代理的collectionView:performAction:forItemAtIndexPath:withSender: 方法来在指定的cell上执行操作。
    列表2-6显示如何防止一个菜单项出现。在这个例子中,collectionView:canPerformAction:forItemAtIndexPath:withSender:方法防止剪切菜单从编辑菜单中出现。它启用复制和粘贴项目,以便用户可以插入内容。

    //Listing 2-6  Selectively disabling actions in the Edit menu
    - (BOOL)collectionView:(UICollectionView *)collectionView
        canPerformAction:(SEL)action
        forItemAtIndexPath:(NSIndexPath *)indexPath
        withSender:(id)sender {
       // Support only copying and pasting of cells.
       if ([NSStringFromSelector(action) isEqualToString:@"copy:"]
      || [NSStringFromSelector(action) isEqualToString:@"paste:"])
          return YES;
    
       // Prevent all other actions.
       return NO;
    }
    

    有关更多使用剪贴板命令的详细信息,请看Text Programming Guide for iOS.

    布局之间的转换

    布局之间的转换的最简单的方式是使用setCollectionViewLayout:animated:方法。然而,如果你需要对转换进行控制或希望其可以交互,使用UICollectionViewTransitionLayout对象。
    UICollectionViewTransitionLayout类是一种特殊类型的布局,当集合视图的布局对象转换为一个新布局是得以初始化。使用转换布局对象,你可以使对象遵循非线性路径,使用不同的计时算法,或根据传入的触摸事件移动。标准类会给一个新布局提供线性转换,但是像UICollectionViewLayout类一样,UICollectionViewTransitionLayout类可以被子类化以创建任何所需的效果。在这样做时,你需要实现与创建自定义布局时相同的方法,并允许你的实现适应于用户的输入,最常用于手势识别器。关于创建自定义布局对象的详细信息,请看Creating Custom Layouts.
    UICollectionViewLayout类提供了几个方法来跟踪布局间的转换,UICollectionViewTransitionLayout对象通过transitionProgress属性来跟踪转换的完成。一旦转换发生,你的代码定期更新此属性以指示转换的完成百分比。例如,使用UICollectionViewTransitionLayout类与诸如手势识别器之类的对象,你可以使用它们在布局之间转换,允许您创建交互式转换。同样,如果您实现自定义转换布局对象UICollectionViewTransitionLayout类提供了两个来跟踪与布局相关的值的方法:updateValue:forAnimatedKey:valueForAnimatedKey:方法。这些方法跟踪可以在转换期间设置和更改的特殊浮点值,以便与布局重要信息进行通信。例如,如果你使用捏合手势在布局之间进行转换,则可以使用这些方法告知转换布局对象在视图需要彼此偏移的位置。
    在你的app中包括UICollectionViewTransitionLayout对象的步骤如下:

    1. 使用initWithCurrentLayout:nextLayout:方法创建标准类或你自己自定义类的实例。
    2. 通过定期修改transitionProgress属性来传达转换的进度。不要忘记在改变了转换的进度之后使用集合视图的invalidateLayout方法来使布局废止。
    3. 在你的集合视图的代理中实现collectionView:transitionLayoutForOldLayout:newLayout:方法,并返回你的转换布局对象。
    4. (可选)使用updateValue:forAnimatedKey:方法修改布局值,以指示与布局对象相关的更改值。这种情况下的稳定值为0。

    官方文档地址
    Designing Your Data Source and Delegate

    相关文章

      网友评论

        本文标题:文档翻译-Collection View - 1.2

        本文链接:https://www.haomeiwen.com/subject/bucqcxtx.html