Designing Your Data Source and Delegate

一、Designing Your Data Source and Delegate

Every collection view must have a data source object. The data source object is the content that your app displays. It could be an object from your app’s data model, or it could be the view controller that manages the collection view. The only requirement of the data source is that it must be able to provide information that the collection view needs, such as how many items there are and which views to use when displaying those items.

The delegate object is an optional (but recommended) object that manages aspects related to the presentation of and interaction with your content. Although the delegate’s main job is to manage cell highlighting and selection, it can be extended to provide additional information. For example, the flow layout extends the basic delegate behavior to customize layout metrics, such as the size of cells and the spacing between them.

每个集合视图都必须有一个数据源对象(data source)。该数据源对象是你的app所显示的内容。它可能是app数据模型中的一个对象,也可能是一个管理集合视图的view controller。数据源的唯一要求是必须能够提供集合视图所需的信息,例如有多少item,以及使用哪些视图来显示这些items。

代理对象(delegate)是一个可选的(但建议)对象,管理内容交互的各个方面。虽然delegate的主要工作是管理cell高亮和选中,但可以将其扩展为提供其他信息。例如,流水布局扩展了基本的代理行为来自定义布局,例如cell的大小和它们之间的间距

The Data Source Manages Your Content

The data source object is the object responsible for managing the content you are presenting using a collection view. The data source object must conform to the UICollectionViewDataSource protocol, which defines the basic behavior and methods that you must support. The job of the data source is to provide the collection view with answers to the following questions:

  • How many sections does the collection view contain?
  • For a given section, how many items does a section contain?
  • For a given section or item, what views should be used to display the corresponding content?

Sections and items are the fundamental organizing principle for collection view content. A collection view typically has at least one section and may contain more. Each section, in turn, contains zero or more items. Items represent the main content you want to present, whereas sections organize those items into logical groups. For example, a photo app might use sections to represent a single album of photos or a set of photos taken on the same day.

The collection view refers to the data it contains using NSIndexPath objects. When trying to locate an item, the collection view uses the index path information provided to it by the layout object. For items, the index path contains a section number and an item number. For supplementary and decoration views, the index path contains whichever values were provided by the layout object. The meaning of the index paths attached to supplementary and decoration views is dependent on your app, though the first index corresponds to a specific section in the data source. These views’ index paths are more about identification than meaning, identifying which view of what kind is currently being considered. So, if for example you have supplementary views that create headers and footers for your sections as seen in the flow layout, the relevant information provided by the index path is the section referenced.

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

集合视图包含多少个section?
对于给定的section,到底包含多少个item?
对于给定的section或item,应该使用哪些视图(其实就是cell)来显示相应的内容?

section和item是集合视图内容的基本组织原则。集合视图通常至少有一个section,可能包含更多。每个section又包含零个或多个item。items表示要呈现的主要内容,而section将这些items组织到逻辑组中。例如,一个照片app可能会使用section来表示照片中的一个相册或在同一天拍摄的一组照片。

集合视图引用NSIndexPath对象所对应的数据。当试图定位一个items时,集合视图使用由layout对象提供给它的索引路径信息。对于items,索引路径包含一个section值和一个item值。对于补充和装饰视图,索引路径包含由layout对象提供的任何值。尽管第一个索引对应于数据源中的特定section,但附加到补充和装饰视图的index path的含义取决于你的应用程序。这些views的索引路径更多是关于识别(identification)而不是意义,标识这个view是哪种类型是正在考虑的。因此,例如,如果你在流水布局中看到为sections创建headers and footers的补充视图,则索引路径提供的相关信息就是所引用的section

==Note: Although standard index paths support multiple levels, the collection view’s cells only supports index paths that are 2-levels deep with “section” and “item” parameters, much like the index paths for the UITableView class. Supplementary views and decoration views can have more complex index paths if necessary. Elements whose index paths are > 1 is interpreted to correspond to the section designated by the first index in the path. Traditionally, only a second index is necessary, but supplementary and decoration views are not restricted to just two. Keep this in mind when designing your data source.==

注意:尽管标准索引路径支持多个级别,但集合视图的cells仅支持具有“section”和“item”参数的2级深度的索引路径,非常类似于UITableView的索引路径。如有必要,补充视图和装饰视图可以具有更复杂的索引路径。索引路径> 1的元素被解释为(is interpreted to)对应于路径中第一个索引指定(designated by)的section。传统上,只有第二个索引是必要的,但补充和装饰视图不仅限于两个。在设计数据源时请记住这一点。

No matter how you arrange the sections and items in your data object, the visual presentation of those sections and items is still determined by the layout object. Different layout objects could present section and item data very differently, as shown in Figure 2-1. In this figure, the flow layout object arranges the sections vertically with each successive section below the previous one. A custom layout could position the sections in a nonlinear arrangement, demonstrating again the separation of the layout from the actual data.

无论你如何安排数据对象中的sections和items,这些sections和items的视觉呈现仍然由layout对象决定。不同的布局对象可能会呈现截然不同的section和item数据,如图2-1所示。在这个图中,流水布局对象垂直排列section,每个连续section在前一个部分的下面。自定义布局可以将section放置在非线性布置中,再次证明(说明)layout与实际数据的分离。

Figure 2-1 Sections arranged according to the arrangement of layout objects

Designing Your Data Objects

An efficient data source uses sections and items to help organize its underlying data objects. Organizing your data into sections and items makes it much easier to implement your data source methods later. And because your data source methods are called frequently, you want to make sure that your implementations of those methods are able to retrieve data as quickly as possible.

One simple solution (but certainly not the only solution) is for your data source to use a set of nested arrays, as shown in Figure 2-2. In this configuration, a top-level array contains one or more arrays representing the sections of your data source. Each section array then contains the data items for that section. Finding an item in a section is a matter of retrieving its section array and then retrieving an item from that array. This type of arrangement makes it easy to manage moderately sized collections of items and retrieve individual items on demand.

高效的数据源使用sectios和items来帮助组织其底层的数据对象。将数据组织到sections和items中,以后可以更容易地实现数据源方法。而且由于你的数据源方法经常被调用,所以你需要确保你的这些方法的实现能够尽可能快的检索(retrieve)数据。

一个简单的解决方案(当然不是唯一的解决方案)是让数据源使用一组嵌套数组,如图2-2所示。在此配置中,顶级数组包含一个或多个表示数据源sction的数组。然后每个section数组包含该items的数据项。在一个section中找到一个项目就是检索它的section数组,然后从该数组中检索一个item。这种安排可以轻松地管理中等规模的items集合,并根据需要检索单个item。

Figure 2-2 Arranging data objects using nested arrays

When designing your data structures, you can always start with a simple set of arrays and move to a more efficient structure as needed. In general, your data objects should never be a performance bottleneck. The collection view usually accesses your data source only to calculate how many objects there are in total and to obtain views for elements that are currently onscreen. If the layout object relies only on data from your data objects, performance could be severely impacted when the data source contains thousands of objects.

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

Telling the Collection View About Your Content

Among the questions asked of your data source by the collection view are how many sections it contains and how many items each section contains. The collection view asks your data source to provide this information when any of the following actions occur:

  • The collection view is displayed for the first time.
  • You assign a different data source object to the collection view.
  • You explicitly call the collection view’s reloadData method.
  • The collection view delegate executes a block using performBatchUpdates:completion: or any of the move, insert, or delete methods.

You provide the number of sections using the numberOfSectionsInCollectionView: method, and the number of items in each section using the collectionView:numberOfItemsInSection: method. You must implement the collectionView:numberOfItemsInSection: method, but if your collection view has only one section, implementing the numberOfSectionsInCollectionView: method is optional. Both methods return integer values with the appropriate information.

If you implemented your data source as shown in Figure 2-2, the implementation of your data source methods could be as simple as those shown in Listing 2-1. In this code, the _data variable is a custom member variable of the data source that stores the top-level array of sections. Obtaining the count of that array yields the number of sections. Obtaining the count of one of the subarrays yields the number of items in the section. (Of course, your own code should do whatever error checking is needed to ensure that the values returned are valid.)

通过集合视图询问你的数据源的问题是它包含多少section以及每个section包含多少items。集合视图会要求你的数据源在发生以下任何操作时提供此信息:

  • 集合视图是第一次显示。
  • 你将一个不同的数据源对象分配给集合视图。
  • 你显式调用集合视图的reloadData方法。
  • delegate执行block方法performBatchUpdates:completion:或任何移动,插入或删除方法。

你可以使用numberOfSectionsInCollectionView:方法提供sections的数量,可以使用collectionView:numberOfItemsInSection:提供每个section中items的数量。你必须实现collectionView:numberOfItemsInSection:方法,但是如果你的集合视图只有一个section,则实现numberOfSectionsInCollectionView:方法是可选的。这两种方法都会返回带有适当信息的整数值。

如果你如图2-2所示实现了数据源,则数据源方法的实现可能如清单2-1所示。在此代码中,_data变量是存储sections的一级数组的自定义成员变量。获取该数组的数量得出section的数量。获取其中一个子数组的计数可得出该section中的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];
}

二、Configuring Cells and Supplementary Views

Another important task of your data source is to provide the views that the collection view uses to display your content. The collection view does not track your app’s content. It simply takes the views you give it and applies the current layout information to them. Therefore, everything that is displayed by the views is your responsibility.

After your data source reports how many sections and items it manages, the collection view asks the layout object to provide layout attributes for the collection view’s content. At some point, the collection view asks the layout object to provide the list of elements in a specific rectangle (often this is the visible rectangle). The collection view uses that list to ask your data source for the corresponding cells and supplementary views. To provide those cells and supplementary views, your code must do the following:

  1. Embed your template cells and views in your storyboard file. (Alternatively, register a class or nib file for each type of supported cell or view.)
  2. In your data source, dequeue and configure the appropriate cell or view when asked.

To ensure that cells and supplementary views are used in the most efficient way possible, the collection view assumes the responsibility of creating those objects for you. Each collection view maintains internal queues of currently unused cells and supplementary views. Instead of creating objects yourself, simply ask the collection view to provide you with the view you want. If one is waiting on a reuse queue, the collection view prepares it and returns it to you quickly. If one is not waiting, the collection view uses the registered class or nib file to create a new one and return it to you. Thus, every time you dequeue a cell or view, you always get a ready-to-use object.

Reuse identifiers make it possible to register multiple types of cells and multiple types of supplementary views. A reuse identifier is a string that you use to distinguish between your registered cell and view types. The contents of the string are relevant only to your data source object. But when asked for a view or cell, you can use the provided index path to determine which type of view or cell you might want and then pass the appropriate reuse identifier to the dequeue method.

数据源的另一个重要任务是提供用于显示内容的视图。集合视图不会跟踪应用的内容。它仅仅使用你提供给它的views,并应用当前的布局信息到views上。因此,视图所展示的所有东西都是你的责任。

在数据源报告了其管理的sections和items数量之后,集合视图会要求layout对象为集合视图的内容提供布局属性。在某些时候,集合视图要求布局对象提供特定矩形中的元素列表(通常这是可见的矩形)。集合视图使用该列表来询问你的数据源的相应cells和补充视图。要提供这些cells和补充视图,你的代码必须执行以下操作:

  1. 将你的模板cells和views嵌入到storyboard文件中。(或者,为每种支持的cell或视图注册类或nib文件。)
  2. 在数据源中,当请求时将dequeue并配置相应的cell或视图。

为确保以最有效的方式使用cells和补充视图,集合视图承担创建这些对象的责任。每个集合视图维护当前未使用的cells和补充视图的内部队列。你可以不要自己创建对象,只需询问集合视图即可为你提供所需的视图。如果正在等待重复使用队列(重用),则集合视图会将其准备好并快速返回给你。如果没有在等待,集合视图使用已注册的类或nib文件创建一个新的并将其返回给你。因此,每次dequeue 一个cell或视图时,都会获得一个随时可以使用的对象。

重用标识符可以注册多种类型的cells和多种类型的补充视图。一个重用标识符是你用你注册的cell和视图类型进行区分的字符串。字符串的内容仅与你的数据源对象有关。但是,当询问一个视图或cell时,可以使用提供的索引路径来确定你想要的视图或cell类型,然后将适当的重用标识符传递给dequeue方法。

Registering Your Cells and Supplementary Views

You can configure the cells and views of your collection view programmatically or in your app’s storyboard file.

Configure cells and views in your storyboard. When configuring cells and supplementary views in a storyboard, you do so by dragging the item onto your collection view and configuring it there. This creates a relationship between the collection view and the corresponding cell or view.

  • For cells, drag a Collection View Cell from the object library and drop it on to your collection view. Set the custom class and the collection reusable view identifier of your cell to appropriate values.
  • For supplementary views, drag a Collection Reusable View from the object library and drop it on to your collection view. Set the custom class and the collection reusable view identifier of your view to appropriate values.

Configure cells programmatically. Use either the registerClass:forCellWithReuseIdentifier: or registerNib:forCellWithReuseIdentifier: method to associate your cell with a reuse identifier. You might call these methods as part of the parent view controller’s initialization process.

Configure supplementary views programmatically. Use either the registerClass:forSupplementaryViewOfKind:withReuseIdentifier: or registerNib:forSupplementaryViewOfKind:withReuseIdentifier: method to associate each kind of view with a reuse identifier. You might call these methods as part of the parent view controller’s initialization process.

Although you register cells using only a reuse identifier, supplementary views require that you specify an additional identifier known as a kind string. Each layout object is responsible for defining the kinds of supplementary views it supports. For example, the UICollectionViewFlowLayout class supports two kinds of supplementary views: a section header view and a section footer view. To identify these two types of views, it defines the string constants UICollectionElementKindSectionHeader and UICollectionElementKindSectionFooter. During layout, the layout object includes the kind string with the other layout attributes for that view type. The collection view then passes the information along to your data source. Your data source then uses both the kind string and the reuse identifier to decide which view object to dequeue and return.

==Note: If you implement your own custom layouts, you are responsible for defining the kinds of supplementary views your layout supports. A layout may support any number of supplementary views, each with its own kind string. For more information about defining custom layouts, see Creating Custom Layouts.==

Registration is a one-time event that must take place before you attempt to dequeue any cells or views. After you’ve registered, you can dequeue as many cells or views as needed without reregistering them. It’s not recommended that you change the registration information after dequeueing one or more items. It is better to register your cells and views once and be done with it.

你可以以纯代码方式或storyboard文件中配置集合视图的cells和views。

在storyboard中配置单元格和视图。在storyboard中配置cells和补充视图时,可以通过将项目拖放到集合视图并在其中进行配置来完成。这将在集合视图和相应的cells或视图之间建立关连。

  • 对于cell,从对象库中拖动一个集合视图cell并将其放到你的集合视图中。将自定义cell所使用的类和重用标识符设置为相应的值。
  • 对于补充视图,从对象库中拖出一个Collection Reusable View并将其放到你的collection视图中。将自定义view所使用的类和重用标识符设置为相应的值。

以纯代码方式配置单元格。使用registerClass:forCellWithReuseIdentifier:或者 registerNib:forCellWithReuseIdentifier:方法将你的cell与重用标识符相关联。你可以将这些方法作为父视图控制器的初始化过程的一部分。

以纯代码方式配置补充视图。使用registerClass:forSupplementaryViewOfKind:withReuseIdentifier:或者 registerNib:forSupplementaryViewOfKind:withReuseIdentifier:方法将每种视图与重用标识符相关联。你可以将这些方法作为父视图控制器的初始化过程的一部分。

尽管只使用重用标识符注册cell,但补充视图要求你额外指定一个类别字符串的重用标识符。每个布局对象负责定义它所支持的补充视图的种类。例如,UICollectionViewFlowLayout类支持两种补充视图:一个section header View和一个section footer view。为了标志这两种类型的视图,它定义了字符串常量UICollectionElementKindSectionHeaderUICollectionElementKindSectionFooter。在布局过程中,布局对象包含类型字符串以及该视图类型的其他布局属性。然后集合视图将信息传递给你的数据源。数据源然后使用种类字符串和重用标识符来决定哪个视图对象dequeue和返回

注意:如果你使用自定义layout,你要负责定义该layout所支持的补充视图。

Dequeueing and Configuring Cells and Views

Your data source object is responsible for providing cells and supplementary views when asked for them by the collection view. The UICollectionViewDataSource protocol contains two methods for this purpose: collectionView:cellForItemAtIndexPath: and collectionView:viewForSupplementaryElementOfKind:atIndexPath:. Because cells are a required element of a collection view, your data source must implement the collectionView:cellForItemAtIndexPath: method, but the collectionView:viewForSupplementaryElementOfKind:atIndexPath: method is optional and dependent on the type of layout in use. In both cases, your implementation of these methods follows a very simple pattern:

  1. Dequeue a cell or view of the appropriate type using the dequeueReusableCellWithReuseIdentifier:forIndexPath: or dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath: method.
  2. Configure the view using the data at the specified index path.
  3. Return the view.

The dequeueing process is designed to relieve you of the responsibility of having to create a cell or view yourself. As long as you registered a cell or view previously, the dequeue methods are guaranteed to never return nil. If there is no cell or view of the given type on a reuse queue, the dequeue method simply creates one using your storyboard or using the class or nib file you registered.

The cell returned to you from the dequeueing process should be in a pristine state and ready to be configured with new data. For a cell or view that must be created, the dequeueing process creates and initializes it using the normal processes—that is, by loading the view from a storyboard or nib file or by creating a new instance and initializing it using the initWithFrame: method. In contrast, an item that wasn’t created from scratch but that was instead retrieved from a reuse queue may already contain data from a previous usage. In that case, dequeue methods call the prepareForReuse method of the item to give it a chance to return itself to a pristine state. When you implement a custom cell or view class, you can override this method to reset properties to default values and perform any additional cleanup.

After your data source dequeues the view, it configures the view with its new data. You can use the index path passed to your data source methods to locate the appropriate data object and then apply that object’s data to the view. After you configure the view, return it from your method and you are done. Listing 2-2 shows a simple example of how to configure a cell. After dequeueing the cell, the method sets the cell’s custom label using the information about the cell’s location and then returns the cell.

数据源对象负责提供cells和supplementary views当集合视图使用它们的时候。UICollectionViewDataSource协议包含2个方法来达到这个目的collectionView:cellForItemAtIndexPath:collectionView:viewForSupplementaryElementOfKind:atIndexPath:。由于cells是集合视图中必须的元素,所以数据源必须实现collectionView:cellForItemAtIndexPath:,但是collectionView:viewForSupplementaryElementOfKind:atIndexPath:方法是可选的,这个取决于使用的layout类型。在这2种情况下,这些方法的实现遵循一个非常简单的模式。

  1. Dequeue一个对应类型的cell或者view使用dequeueReusableCellWithReuseIdentifier:forIndexPath:或者dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:方法。
  2. 在指定的 index path配置view使用的数据
  3. 返回这个view

dequeueing的过程是设计出来减轻自己的职责,不用自己创建cell和view了。只要你之前注册了cell或者view,dequeue方法保证永远不会返回nil。如果在重用队列中没有给定类型的cell或者view时,dequeue方法会使用storyboard,类或者nib文件来创建一个。

从dequeue方法返回的cell应该是一个原始的状态,并且准备被新数据配置。对于必须要创建的cell或view(重用队列中没有),dequeue过程会使用正常过程创建并且初始化,也就是说,从storyboard或nib文件或创建一个实例并且使用initWithFrame:方法来初始化。相比之下,不是从头开始创建的item,而是从重用队列中索引的item可能包含之前使用的数据。在这个情况下,dequeue方法调用item的prepareForReuse方法,使其有机会回到初始状态。当你实现一个自定义的cell或者view是,你可以重写这个方法来复位属性值为默认值,并且执行其他的清理工作。

当数据源dequeues出view之后,会使用新数据配置这个view。你可以使用index path传递给你的数据源方法来定位相应的数据对象,并且应用对象的数据到view身上。在配置完view后,通过你的方法返回它,你的活就做完了。清单2-2展示了如何配置一个cell的简单事例。在dequeuecell的时候,这个方法使用cell的位置信息设置了cell的自定义label并且返回这个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;
}

==Note: When returning views from your datasource, always return a valid view. Returning nil, even if for some reason the view that is being asked for should not be displayed, causes an assertion and your app terminates because the layout object expects valid views to be returned by these methods.==
注意:当从数据源返回views的时候,一定要返回一个有效的view。返回nil会触发一个断言导致你的app终止,因为layout对象希望通过这个方法返回有效的views。即使由于某些原因当询问到这个cell时,它不需要显示出来也不行

三、Inserting, Deleting, and Moving Sections and Items

To insert, delete, or move a single section or item, follow these steps:

  1. Update the data in your data source object.
  2. Call the appropriate method of the collection view to insert or delete the section or item.

It is critical that you update your data source before notifying the collection view of any changes. The collection view methods assume that your data source contains the currently correct data. If it does not, the collection view might receive the wrong set of items from your data source or ask for items that are not there and crash your app.

When you add, delete, or move a single item programmatically, the collection view’s methods automatically create animations to reflect the changes. If you want to animate multiple changes together, though, you must perform all insert, delete, or move calls inside a block and pass that block to the performBatchUpdates:completion: method. The batch update process then animates all of your changes at the same time and you can freely mix calls to insert, delete, or move items within the same block.

Listing 2-3 shows a simple example of how to perform a batch update to delete the currently selected items. The block passed to the performBatchUpdates:completion: method first calls a custom method to update the data source. It then tells the collection view to delete the items. Both the update block and the completion block you provide are executed synchronously.

插入,删除,移动一个section或item需要遵守以下步骤:

  1. 在你的数据源对象中更新数据
  2. 调用相应的集合视图的插入、删除section或item方法

不论任何改变,在通知集合视图之前就更新你的数据源是非常危险的。集合视图的方法假设(assume)你的数据源包含当前正确的数据。如果不是这样,集合视图可能接受错误的items(从你的数据源)或者询问不是这个位置的items,然后你的app就会直接奔溃。

当你纯代码添加,删除,或者移动一个item,集合视图的方法会自动创建动画来反应这些变化。但是,如果想要一起动画多个改变,你必须执行所有的插入,删除,或者移动在一个block中,并且传递这个block给performBatchUpdates:completion:方法。批量更新(batch update)过程然后在同一时间动画化所有更改,并且可以自由混合调用以在同一个块内插入,删除或移动item。

清单2-3展示了一个如何批量更新删除当前选中的items的示例。传递给he performBatchUpdates:completion:方法的block首先调用一个自定义方法更新数据源。然后告诉集合视图去删除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];

四、Managing the Visual State for Selections and Highlights

Collection views support single-item selection by default and can be configured to support multiple-item selection or have selections disabled altogether. The collection view detects taps inside its bounds and highlights or selects the corresponding cell accordingly. For the most part, the collection view modifies only the properties of a cell to indicate that it is selected or highlighted; it does not change the visual appearance of your cells, with one exception. If a cell’s selectedBackgroundView property contains a valid view, the collection view shows that view when the cell is highlighted or selected.

Listing 2-4 shows code that could be incorporated into your implementation of a custom collection view cell to facilitate a changing appearance for highlighted and selected states. The cell’s backgroundView property will always be the default view when the cell loads for the first time and when the cell is either not highlighted or not selected. The selectedBackgroundView property replaces the default background view whenever a cell is highlighted or selected. In this case, the cell’s background color would be changed from red to white when selected or highlighted.

集合视图默认支持单选,并且可以配置支持多选或者完全禁用选择。集合视图检查在它自身bounds范围内的点击(taps)并且高亮或者选择相应cell。大多数情况下,集合视图仅修改cell的属性以指示它被选中或高亮; 它不会改变你的cell的视觉外观,只有一个例外。如果cell的selectedBackgroundView属性包含有效的视图,则集合视图将在cell被高亮或选中时显示该视图。

清单2-4展示的代码可以合并到自定义cell的实现中去,以便高亮或选中时改变外观。,cell的backgroundView属性将一直是其第一次加载时候默认视图和cell没有高亮或者选中时的默认视图。selectedBackgroundView属性物理在cell选中或者选中的时候都会替换默认背景。在这种情况下,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;

The collection view’s delegate provides the collection view with the following methods to facilitate highlighting and selecting:
集合视图的delegate提供以下的方法以便处理高亮和选中

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

These methods provide you with many opportunities to tweak the highlighting/selecting bahavior of your collection view to the exact desired specifications.
这些方法为你提供了许多机会来调整你的收藏视图的高亮/选中行为,以达到所需的规格。

For example, if you prefer to draw the selection state of a cell yourself, you can leave the selectedBackgroundView property set to nil and apply any visual changes to the cell using your delegate object. You would apply the visual changes in the collectionView:didSelectItemAtIndexPath:: method and remove them in the collectionView:didDeselectItemAtIndexPath: method.
例如,如果你更喜欢自己绘制cell选中状态,你可以设置selectedBackgroundView属性为nil,并且使用你的代理对象将任何可视化更改应用于cell。你将会在collectionView:didSelectItemAtIndexPath:方法中应用视觉改变,并且在collectionView:didDeselectItemAtIndexPath:中移除它们。

If you prefer to draw the highlight state yourself, you can override the collectionView:didHighlightItemAtIndexPath: and collectionView:didUnhighlightItemAtIndexPath: delegate methods and use them to apply your highlights. If you also specified a view in the selectedBackgroundView property, you should make your changes to the content view of the cell to ensure your changes are visible. Listing 2-5 shows a simple way of changing the highlight using the content view’s background color.
如果你更愿意自己绘制cell的高亮状态,你可以重写collectionView:didHighlightItemAtIndexPath:collectionView:didUnhighlightItemAtIndexPath:代理方法,并且使用它们来应用高亮显示。如果你也用selectedBackgroundView属性指定了一个view,你应该对cell的内容作出改变,以确保你的更改是可见的。清单2-5展示了使用view的背景色来改变高亮状态的一个简单方式。

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;
}

There is a subtle but important distinction between a cell’s highlighted state and its selected state. The highlighted state is a transitional state that you can use to apply visible highlights to the cell while the user’s finger is still touching the device. This state is set to YES only while the collection view is tracking touch events over the cell. When touch events stop, the highlighted state returns to the value NO. By contrast, the selected state changes only after a series of touch events has ended—specifically, when those touch events indicated that the user tried to select the cell.

对于cell的高亮和选中状态有一个细微的但是非常重要的区别。高亮状态是一个过渡状态,当用户的手指仍然触摸设备的时候,你可以使用该状态来应用可见的高亮到cell上。这个状态只有在集合视图跟踪cell上的触摸事件时会被设置成YES。当触摸事件停止时,高亮状态返回的是NO。相比之下,选中状态只有在一系列触摸事件结束之后才会发生,当这些触摸事件反应用户试图选中这个cell

Figure 2-3 illustrates the series of steps that occurs when a user touches an unselected cell. The initial touch-down event causes the collection view to change the highlighted state of the cell to YES, although doing so does not automatically change the appearance of the cell. If the final touch up event occurs in the cell, the highlighted state returns to NO and the collection view changes the selected state to YES. When the user changes the selected state, the collection view displays the view in the cell’s selectedBackgroundView property, but this is the only visual change that the collection view makes to the cell. Any other visual changes must be made by your delegate object.

图2-3 表明了当用户触摸一个未被选中的cell时的一系列步骤。最开始的touch-down事件引起集合视图改变cell的高亮状态为YES,尽管这么做并没有自动的改变cell的外观。如果最终的touch-up事件发生在cell的内部,高亮状态返回的是NO,并且集合视图把选中状态变成YES。当用户的行为改变了选中状态时,集合视图会展示cell的selectedBackgroundView属性设置的view,但是这个仅仅是集合视图对cell的视觉上的改变。任何其他的视觉改变都必须通过你的delegate对象

Whether the user is selecting or deselecting a cell, the cell’s selected state is always the last thing to change. Taps in a cell always result in changes to the cell’s highlighted state first. Only after the tap sequence ends and any highlights applied during that sequence are removed, does the selected state of the cell change. When designing your cells, you should make sure that the visual appearance of your highlights and selected state do not conflict in unintended ways.

不论用户是选中还是取消选中cell,cell的selected状态都是最后才变化的。在cell内部的点击(tap)总是首先导致cell的hightlighted状态的改变。只有当tap事件都发生后,并且在这一系列事件中导致高亮发生了,cell的选中状态才会触发。在设计cell的时候,你需要确保高亮和选中的视觉样式不会冲突

五、Showing the Edit Menu for a Cell

When the user performs a long-tap gesture on a cell, the collection view attempts to display an Edit menu for that cell. The Edit menu can be used to cut, copy, and paste cells in the collection view. Several conditions must be met before the Edit menu can be displayed:

  • The delegate must implement all three methods related to handling actions:
    collectionView:shouldShowMenuForItemAtIndexPath:
    collectionView:canPerformAction:forItemAtIndexPath:withSender:
    collectionView:performAction:forItemAtIndexPath:withSender:

  • The collectionView:shouldShowMenuForItemAtIndexPath: method must return YES for the indicated cell.

  • The collectionView:canPerformAction:forItemAtIndexPath:withSender: method must return YES for at least one of the desired actions. The collection view supports the following actions:

    cut:
    copy:
    paste:

If these conditions are met and the user chooses an action from the menu, the collection view calls the delegate’s collectionView:performAction:forItemAtIndexPath:withSender: method to perform the action on the indicated item.

当在cell上有一个长按手势(long-tap),集合视图会试图去显示一个“编辑”菜单。这个“编辑”菜单在集合视图中可以用来剪切,复制,和粘贴cells。在“编辑”菜单可以被显示的时候有几个条件必须要满足:

  • delegate必须实现处理事件的3个方法
    collectionView:shouldShowMenuForItemAtIndexPath:
    collectionView:canPerformAction:forItemAtIndexPath:withSender:
    collectionView:performAction:forItemAtIndexPath:withSender:
  • collectionView:shouldShowMenuForItemAtIndexPath:方法必须返回YES。
  • collectionView:canPerformAction:forItemAtIndexPath:withSender:方法必须返回至少一种期望的action,集合视图支持下面3种actions:cut:copy:paste:

Listing 2-6 shows how to prevent one of the menu items from appearing. In this example, the collectionView:canPerformAction:forItemAtIndexPath:withSender: method prevents the Cut menu item from appearing in the Edit menu. It enables the Copy and Paste items so that the user can insert content.

清单2-6展示了如何防止菜单的出现。在这个示例中,collectionView:canPerformAction:forItemAtIndexPath:withSender:方法在“编辑”菜单阻止剪切菜单项。但是它可以copy和paste这些items,因此用户可以插入内容

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;
}

For more information on working with the pasteboard commands, see Text Programming Guide for iOS.

六、Transitioning Between Layouts

The easiest way to transition between layouts is by using the setCollectionViewLayout:animated: method. However, if you require control of the transition or want it to be interactive, use a UICollectionViewTransitionLayout object.

The UICollectionViewTransitionLayout class is a special type of layout that gets installed as the collection view’s layout object when transitioning to a new layout. With a transition layout object, you can have objects follow a non linear path, use a different timing algorithm, or move according to incoming touch events. The standard class provides a linear transition to a new layout, but like the UICollectionViewLayout class, the UICollectionViewTransitionLayout class can be subclassed to create any desired effect. In doing so, you need to implement the same methods you would when creating a custom layout and allow your implementation to adapt to input from the user, most often from a gesture recognizer. For more information about creating custom layout objects, see Creating Custom Layouts.

The UICollectionViewLayout class provides several methods for tracking the transition between layouts. UICollectionViewTransitionLayout objects track the completion of a transition through the transitionProgress property. As the transition occurs, your code updates this property periodically to indicate the completion percentage of the transition. For example, using the UICollectionViewTransitionLayout class in conjunction with objects like gesture recognizers, which you can use to transition between layouts, allows you to create interactive transitions. As well, if you implement a custom transition layout object, the UICollectionViewTransitionLayout class provides two methods for tracking values relevant to your layout: the updateValue:forAnimatedKey: and valueForAnimatedKey: methods. These methods track special floating point values that you can set and change during a transition to communicate to the layout important information. For example, if you transitioned between layouts using a pinch gesture, you could use these methods to tell the transition layout object at what offset the view’s need to be from one another.

The steps for including a UICollectionViewTransitionLayout object in your app are as follows:

  1. Create an instance of the standard class or your own custom class using the initWithCurrentLayout:nextLayout: method.
  2. Communicate the progress of the transition by periodically modifying the transitionProgress property. Do not forget to invalidate the layout using the collection view’s invalidateLayout method after changing the transition’s progress.
  3. Implement the collectionView:transitionLayoutForOldLayout:newLayout: method in your collection view’s delegate and return your transition layout object.
  4. Optionally modify values for your layout using the updateValue:forAnimatedKey: method to indicate changed values relevant to your layout object. The stable value in this case is 0.

在layouts之间过渡(转换)的最简单的办法是使用setCollectionViewLayout:animated:方法。但是,如果你希望能控制这个过渡或者想要在过渡时能够实现用户间的交互,那就使用一个UICollectionViewTransitionLayout对象。

UICollectionViewTransitionLayout类是一个特殊的layout类型,当过渡到一个新的layout时,可以从集合视图的layouts对象中获取已经设置的layout对象。通过一个transition layout对象,你可以使对象遵循非线性的路径,使用一个不同的时间算法,或者根据发生的touch事件来移动。标准的类在一个新的layout中提供了一个线性的过渡,但是和UICollectionViewLayout一样,UICollectionViewTransitionLayout类可以通过子类化来实现想要的效果。这么做时,你需要实现和创建自定义layout时同样的方法,并且允许你的实现适应来着用户的输入(通常是来自手势识别器)。关于更详细的自定义布局,查看章节《Creating Custom Layouts》

UICollectionViewLayout类提供来几个方法来跟踪layouts之间的过渡。UICollectionViewTransitionLayout对象通过transitionProgress属性来一个过渡的过程。当过渡发生时,你的代码阶段的更新这个属性来反映过渡的完成百分比。例如,使用UICollectionViewTransitionLayout类和手势识别器对象联合使用,允许你创建交互性的过渡。同样,如果你实现一个自定义的transition layout对象,UICollectionViewTransitionLayout类提供了2个方法来跟踪布局相关的值的改变updateValue:forAnimatedKey:valueForAnimatedKey:方法。这些方法跟踪特殊浮点值,你可以在过渡过程中设置和更改浮点值,以便与layout进行通信。例如,如果使用捏合手势在布局之间进行过渡,则可以使用这些方法来告诉transition layout对象视图需要相互偏移的偏移量。

实现UICollectionViewTransitionLayout对象的步骤如下:

  1. 使用initWithCurrentLayout:nextLayout:方法创建标准类的实例或你自己的自定义类。
  2. 通过定期修改transitionProgress属性来沟通过渡的进展。在改变过渡的进度之后,不要忘记使用集合视图的invalidateLayout方法使lyaout无效
  3. 在你的集合视图的delegate中实现collectionView:transitionLayoutForOldLayout:newLayout:方法,并返回你的transition layout对象
  4. 也可以使用updateValue:forAnimatedKey:方法修改layout的值,以指示与布局对象相关的改变值。这种情况下的稳定值是0
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容