Before you start building custom layouts, consider whether doing so is really necessary. The UICollectionViewFlowLayout
class provides a significant amount of behavior that has already been optimized for efficiency and that can be adapted in several ways to achieve many different types of standard layouts. The only times to consider implementing a custom layout are in the following situations:
The layout you want looks nothing like a grid or a line-based breaking layout (a layout in which items are placed into a row until it’s full, then continue on to the next line until all items are placed) or necessitates scrolling in more than one direction.
You want to change all of the cell positions frequently enough that it would be more work to modify the existing flow layout than to create a custom layout.
The good news is that, from an API perspective, implementing a custom layout is not difficult. The hardest part is performing the calculations needed to determine the positions of items in the layout. When you know the locations of those items, providing that information to the collection view is straightforward.
在你创建自定义布局之前,想清楚是不是真的必要。UICollectionViewFlowLayout类提供了很过已经功能优化好的,有多重方法可是实现不同类型的标准布局。仅有的需要完成自动布局的情况大约如下:
For custom layouts, you want to subclass UICollectionViewLayout
, which provides you with a fresh starting point for your design. Only a handful of methods provide the core behavior for your layout object and are required in your implementation. The rest of the methods are there for you to override as needed to tweak the layout behavior. The core methods handle the following crucial tasks:
Specify the size of the scrollable content area.
Provide attribute objects for the cells and views that make up your layout so that the collection view can position each cell and view.
Although you can create a functional layout object that implements just the core methods, your layout is likely to be more engaging if you implement several of the optional methods as well.
The layout object uses information provided by its data source to create the collection view’s layout. Your layout communicates with the data source by calling methods on thecollectionView
property, which is accessible in all of the layout’s methods. Keep in mind what your collection view knows and doesn’t know during the layout process. Because the layout process is under way, the collection view cannot track the layout or positioning of views. So even though the layout object will not restrict you from calling any of the collection view’s methods, refrain from relying on the collection view for anything other than the data necessary to compute your layout.
The collection view works directly with your custom layout object to manage the overall layout process. When the collection view determines that it needs layout information, it asks your layout object to provide it. For example, the collection view asks for layout information when it is first displayed or is resized. You can also tell the collection view to update its layout explicitly by calling the invalidateLayout
method of the layout object. That method throws away the existing layout information and forces the layout object to generate new layout information.
invalidateLayout
方法来更新布局。这个方法扔掉现有的布局信息,强制产生新的布局信息。
Note: Be careful not to confuse the layout object’s invalidateLayout
method with the collection view’s reloadData
method. Calling the invalidateLayout
method does not necessarily cause the collection view to throw out its existing cells and subviews. Rather, it forces the layout object to recompute all of its layout attributes as is necessary when moving and adding or deleting items. If data within the data source has changed, the reloadData
method is appropriate. Regardless of how you initiate a layout update, the actual layout process is the same.
提示:注意不要把布局对象的invalidateLayout 方法和集合视图的 reloadData 方法。调用invalidateLayout方法并不一定会导致集合视图抛弃现有的单元格和子视图。相反,当移动或删除单元格的时候,它强制布局对象重新计算所有布局属性因为这是必要的。如果数据源改变了,使用reloadData 是合适的,不论你怎样初始化布局,实际的布局过程都是相同的。
During the layout process, the collection view calls specific methods of your layout object. These methods are your chance to calculate the position of items and to provide the collection view with the primary information it needs. Other methods may be called, too, but these methods are always called during the layout process in the following order:
Use the prepareLayout
method to perform the up-front calculations needed to provide layout information.
Use the collectionViewContentSize
method to return the overall size of the entire content area based on your initial calculations.
Use the layoutAttributesForElementsInRect:
method to return the attributes for cells and views that are in the specified rectangle.
The prepareLayout
method is your opportunity to perform whatever calculations are needed to determine the position of the cells and views in the layout. At a minimum, you should compute enough information in this method to be able to return the overall size of the content area, which you return to the collection view in step 2.
The collection view uses the content size to configure its scroll view appropriately. For instance, if your computed content size expands past the bounds of the current device’s screen both vertically and horizontally, the scroll view adjusts to allow scrolling in both directions simultaneously. Unlike the UICollectionViewFlowLayout
, it does not by default adjust the layout of content to scroll in only one direction.
Based on the current scroll position, the collection view then calls your layoutAttributesForElementsInRect:
method to ask for the attributes of the cells and views in a specific rectangle, which may or may not be the same as the visible rectangle. After returning that information, the core layout process is effectively complete.
After layout finishes, the attributes of your cells and views remain the same until you or the collection view invalidates the layout. Calling the invalidateLayout
method of your layout object causes the layout process to begin again, starting with a new call to the prepareLayout
method. The collection view can also invalidate your layout automatically during scrolling. If the user scrolls its content, the collection view calls the layout object’s shouldInvalidateLayoutForBoundsChange:
method and invalidates the layout if that method returns YES
.
Note: It is useful to remember that calling the invalidateLayout
method does not begin the layout update process immediately. The method merely marks the layout as being inconsistent with the data and in need of being updated. During the next view update cycle, the collection view checks to see whether its layout is dirty and updates it if it is. In fact, you can call the invalidateLayout
method multiple times in quick succession without triggering an immediate layout update each time.
The attributes objects that your layout is responsible for are instances of the UICollectionViewLayoutAttributes
class. These instances can be created in a variety of different methods in your app. When your app is not dealing with thousands of items, it makes sense to create these instances while preparing the layout, because the layout information can be cached and referenced rather than computed on the fly. If the costs of computing all the attributes up front outweighs the benefits of caching in your app, it is just as easy to create attributes in the moment when they are requested.
Regardless, when creating new instances of the UICollectionViewLayoutAttributes
class, use one of the following class methods:
layoutAttributesForCellWithIndexPath:
layoutAttributesForSupplementaryViewOfKind:withIndexPath:
layoutAttributesForDecorationViewOfKind:withIndexPath:
layoutAttributesForCellWithIndexPath:
layoutAttributesForSupplementaryViewOfKind:withIndexPath:
layoutAttributesForDecorationViewOfKind:withIndexPath:
You must use the correct class method based on the type of the view being displayed because the collection view uses that information to request the appropriate type of view from the data source object. Using the incorrect method causes the collection view to create the wrong views in the wrong places and your layout does not appear as intended.
After creating each attributes object, set the relevant attributes for the corresponding view. At a minimum, set the size and position of the view in the layout. In cases where the views of your layout overlap, assign a value to the zIndex
property to ensure a consistent ordering of the overlapping views. Other properties let you control the visibility or appearance of the cell or view and can be changed as needed. If the standard attributes class does not suit your app’s needs, you can subclass and expand it to store other information about each view. When subclassing layout attributes, it’s required that you implement the isEqual:
method for comparing your custom attributes because the collection view uses this method for some of its operations.
For more information about layout attributes, see UICollectionViewLayoutAttributes Class Reference.
At the beginning of the layout cycle, the layout object calls prepareLayout
before beginning the layout process. This method is your chance to calculate information that later informs your layout. The prepareLayout
method is not required to implement a custom layout but is provided as an opportunity to make initial calculations if necessary. After this method is called, your layout must have enough information to calculate the collection view’s content size, the next step in the layout process. The information, however, can range from this minimum requirement to creating and storing all the layout attributes objects your layout will use. Use of the prepareLayout
method is subject to the infrastructure of your app and to what makes sense to compute up front versus what to compute upon request. For an example of what the prepareLayout
method might look like, see Preparing the Layout.
During the final step of the layout process, the collection view calls your layout object’s layoutAttributesForElementsInRect:
method. The purpose of this method is to provide layout attributes for every cell and every supplementary or decoration view that intersects the specified rectangle. For a large scrollable content area, the collection view may just ask for the attributes of items in the portion of that content area that is currently visible. In Figure 5-2, the currently visible content that your layout object needs to create attribute objects for is cells 6 through 20 along with the second header view. You must be prepared to provide layout attributes for any portion of your collection view content area. Such attributes might be used to facilitate animations for inserted or deleted items.
Because the layoutAttributesForElementsInRect:
method is called after your layout object’s prepareLayout
method, you should already have most of the information you need in order to return or create the required attributes. The implementation of your layoutAttributesForElementsInRect:
method follows these steps:
Iterate over the data generated by the prepareLayout
method to either access cached attributes or create new ones.
Check the frame of each item to see whether it intersects the rectangle passed to the layoutAttributesForElementsInRect:
method.
For each intersecting item, add a corresponding UICollectionViewLayoutAttributes
object to an array.
Return the array of layout attributes to the collection view.
UICollectionViewLayoutAttributes
对象加入数组中。 Depending on how you manage your layout information, you might create UICollectionViewLayoutAttributes
objects in your prepareLayout
method or wait and do it in yourlayoutAttributesForElementsInRect:
method. While forming an implementation that matches the needs of your application, keep in mind the benefits of caching layout information. Computing new layout attributes repeatedly for cells is an expensive operation, one that can have noticeably detrimental effects on your app’s performance. That said, when the amount of items your collection view manages is large, it may make more sense (for performance) to create the layout attributes when requested. It’s simply a matter of figuring out which strategy makes most sense for your app.
依据你怎样管理你的布局信息,你可以在你的preparelayout方法中创建一个 UICollectionViewLayoutAttributes
对象,或等待它在layoutAttributesForElementsInRect:方法中创建。当你构建一个匹配你应用需求的实现的时候,记住缓存布局信息的好处。反复计算单元格的新的布局属性是一种很浪费的操作,这会明显影响你的应用的表现。这就是说,当你的集合视图管理很多单元格的时候,在请求的时候创建布局属性是很必要的(对于性能)。为你的程序找到最有效的方法其实是很简单的。
Note: Layout objects also need to be able to provide layout attributes on demand for individual items. The collection view might request that information outside of the normal layout process for several reasons, including to create appropriate animations. For more information about providing layout attributes on demand, see Providing Layout Attributes On Demand.
For a specific example of how one might implement layoutAttributesForElementsInRect:
, see Providing Layout Attributes.
layoutAttributesForElementsInRect:
,方法的例子,请看 Providing Layout Attributes。
The collection view periodically asks your layout object to provide attributes for individual items outside of the formal layout process. For example, the collection view asks for this information when configuring insertion and deletion animations for an item. Your layout object must be prepared to provide the layout attributes for each cell, supplementary view, and decoration view it supports. You do this by overriding the following methods:
layoutAttributesForItemAtIndexPath:
layoutAttributesForSupplementaryViewOfKind:atIndexPath:
layoutAttributesForDecorationViewOfKind:atIndexPath:
layoutAttributesForItemAtIndexPath:
layoutAttributesForSupplementaryViewOfKind:atIndexPath:
layoutAttributesForDecorationViewOfKind:atIndexPath:
Your implementation of these methods should retrieve the current layout attributes for the given cell or view. Every custom layout object is expected to implement thelayoutAttributesForItemAtIndexPath:
method. If your layout does not contain any supplementary views, you do not need to override thelayoutAttributesForSupplementaryViewOfKind:atIndexPath:
method. Similarly, if it does not contain decoration views, you do not need to override thelayoutAttributesForDecorationViewOfKind:atIndexPath:
method. When returning attributes, you should not update the layout attributes. If you need to change the layout information, invalidate the layout object and let it update that data during a subsequent layout cycle.
layoutAttributesForSupplementaryViewOfKind:atIndexPath:
方法。类似的,如果不包括装饰视图,你不需要重写
layoutAttributesForDecorationViewOfKind:atIndexPath:
方法。当返回属性的时候,你不应该更新布局属性。如果你需要更改布局属性,无效化布局对象同时在下一个布局周期更新数据。
There are two ways to link your custom layout to the collection view: programmatically or through storyboards. The collection view links to its layout through a writable property,collectionViewLayout
. To set the layout to your custom implementation, set your collection view’s layout property to an instance of your custom layout object. Listing 5-1 shows the line of code needed.
有两种方法连接你的自定义布局和集合视图:编程方法和通过故事板。集合视图连接他的布局对象通过一个可写的属性,collectionViewLayout。给你的实现设置布局,设置你自定义布局对象的实例到你的的集合视图的布局属性。就像5-1展示的那一行代码。
Linking your custom layout
self.collectionView.collectionViewLayout = [[MyCustomLayout alloc] init]; |
Otherwise, from your storyboard, open the Document Outline panel and select your collection view (it is listed in the drop-down menu for your controller). With the collection view selected, open the Attributes inspector in the Utilities pane, and underneath the section labeled Collection View change the Layout option from Flow to Custom. The option beneath it changes from Scroll Direction to Class, and you can now select your custom layout class.
Providing layout attributes for each cell and view during the layout process is required, but there are other behaviors that can improve the user experience with your custom layout. Implementing these behaviors is optional but recommended.
Supplementary views are separate from the collection view’s cells and have their own set of layout attributes. Like cells, these views are provided by the data source object, but their purpose is to enhance the main content of your app. For example, the UICollectionViewFlowLayout
uses supplementary views for section headers and footers. Another app could use supplementary views to give each cell its own text label to display information about that cell. Like collection view cells, supplementary views undergo a recycling process to optimize the amount of resources used by the collection view. Therefore, all supplementary views used in your app should be subclassed from the UICollectionReusableView
class.
The steps for adding supplementary views to your layouts are as follows:
Register your supplementary view to the collection view’s layout object using either the registerClass:forSupplementaryViewOfKind:withReuseIdentifier:
orregisterNib:forSupplementaryViewOfKind:withReuseIdentifier:
method.
In your data source, implement collectionView:viewForSupplementaryElementOfKind:atIndexPath:
. Because these views are reusable, calldequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:
to dequeue, or create, a new reusable view and set any necessary data before returning it.
Create layout attributes objects for your supplementary views just as you do for cells.
Include these layout attributes objects in the array of attributes returned by the layoutAttributesForElementsInRect:
method.
Implement the layoutAttributesForSupplementaryViewOfKind:atIndexPath:
method to return the attributes object for the specified supplementary view whenever queried.
The process for creating the attributes objects for supplementary views in your custom layout is nearly identical to the process for cells, but differs in that a custom layout can have multiple types of supplementary views but is restricted to one type of cell. This is because supplementary views are meant to enhance the main content and are therefore separate from it. There are many ways in which an app’s content can be supplemented, and so each of the supplementary view’s methods specifies which kind of view is being addressed to distinguish it from the others and allow your layout to compute its attributes correctly based on its type. When registering a supplementary view for use, the string you provide is used by the layout object to distinguish the view from others. For an example of incorporating supplementary views into your custom layout, see Incorporating Supplementary Views.