IOS 集合视图指南7:自定义布局范例

Custom Layouts: A Worked Example (自定义布局的一个例子)

Creating a custom collection view layout is simple with straightforward requirements, but the implementation details of the process may vary. Your layout must produce layout attributes objects for every view your collection view contains. The order in which these attributes are created depend on the nature of your application. With a collection view that houses thousands of items, computing and caching layout attributes upfront is a time-consuming process, so it makes more sense to create attributes only when requested for a specific item. For an application with fewer items, computing layout information once and caching it to reference whenever attributes are requested can save your application a lot of unneeded recalculation. The worked example in this chapter belongs in the second category.

创建一个自定义的集合视图的布局是一个直接而简单的要求,在实现过程的细节上可能会有不同。你的布局必须为你的集合视图的每个视图内容生产出布局属性对象。这些要求的属性根据你的应用的需求产生。一个包含了上千元素的集合视图,计算并缓存布局属性是一个消耗时间的过程,所以当要求元素的时候才创建布局属性是很有意义的。对一个包含较少元素的应用,一次性的计算布局信息并缓存,可以为你的应用节省很多不急要的计算。本例属于第二种情况。

Keep in mind that the provided sample code is by no means the definitive way to create a custom layout. Before you begin creating your custom layout, take time to devise an implementation structure that makes the most sense for your app to get the best performance. For a conceptual overview of the layout customizing process, see Creating Custom Layouts.

记住示例代码并不是创建自定义布局的唯一方法。在你开始创建你的自定义布局之前,花些时间设计一个最合适你应用发挥最好的结构。一个自定义布局概念过程的概述,请见:Creating Custom Layouts

Because this chapter presents the custom layout implementation in a specific order, follow along the example from top to bottom with a specific implementation goal in mind. This chapter focuses on creating a custom layout, not on implementing a complete app. Therefore, implementations of the views and controllers used to create the final product are not provided. The layout uses custom collection view cells as its cells and a custom view for creating the lines connecting cells to one another. Creating custom cells and views for collection views, as well as the requirements for using a collection view, are covered in previous chapters. To review this information, see Collection View Basics and Designing Your Data Source and Delegate.

因为这个章节展现了在一个特定的要求下实现自定义布局,跟着例子从头到尾来实现一个心中特定的目标。这一章专注于创建自定义布局,而不是完成一个完整的应用。因此并不提供完成的视图和控制器。布局使用自定义集合视图单元格作为他的单元格和和为了创建单元格之间连线的自定义视图。为集合视图创建单元格和视图,以及集合视图使用的请求,都包含在之前的章节,请看:Collection View Basics and Designing Your Data Source and Delegate.

The Concept(思路)

The purpose of this worked example is to implement a custom layout for displaying a hierarchichal tree of information such as the diagram seen in Figure 6-1. The example provides snippets of code followed by an explanation of the code, along with the point in the customization process you have reached. Each section of the collection view constitutes one level of depth into the tree: Section 0 contains only the NSObject cell. Section 1 contains all of the children cell’s of NSObject. Section 2 contains all of the children cells of those children, and so on. Each of the cells is a custom cell, with a label for the associated class name, and the connections between cells are supplementary views. Because the connector view class must determine how many connections to draw, it needs access to the data in our data source. It therefore makes sense to implement these connections as supplementary views and not decoration views.

这个例子的目的是实现一个信息的分级树像图6-1展示的突变那样。这个例子在你自定义的过程中,提供了分段的代码,和相应的解释。集合视图的每一节构成了树的一层:第0节只包含了 nsobject 单元格。第一节包含了nsobject的所有子单元格,以及其他。每个单元格都是一个自定义单元格,有一个表现展示了类名,单元格之间的连接的是增补视图。因为这个连接视图类必须确定有多少个连接需要画出来,所以它需要访问数据源中的数据。因此需要用增补视图而不是装饰视图来实现连接物。

Figure 6-1  Class hierarchy

Initialization(初始化)

The first step in creating a custom layout is to subclass the UICollectionViewLayout class. Doing so provides you with the foundations necessary to build a custom layout.

创建自定义布局的第一步是子类化 UICollectionViewLayout类。这样做可以给你自定义的布局提供必要的基础。

For this example, a custom protocol is necessary to inform the layout’s spacing between certain items. If the attributes of specific items require extra information from the data source, it’s best to implement a protocol for a custom layout rather than to initiate a direct connection to the data source. Your resulting layout is more robust and reusable; it won’t be attached to a specific data source but will instead respond to any object that implements its protocol.

在这个例子中,需要自定义的协议来构建元素之间的距离。如果制定的元素要从数据源获取额外的信息。最好的创建一个自定义的布局而不是直接初始化数据源。你的布局将会更强大和复用性;塔不会和特定的数据源连接而是响应任何实现这个协议的对象。

Listing 6-1 shows the necessary code for the custom layout’s header file. Now, any class that implements the MyCustomProtocol protocol can utilize the custom layout, and the layout can query that class for the information it needs.

列表6-1展示了创建一个自定义布局的头文件需要的必要代码。现在任何实现MyCustomProtocol 协议的类可以利用自定义布局,同时布局可以在类里面找到它需要的相应的信息。

Listing 6-1  Connecting to the custom protocol

@interface MyCustomLayout : UICollectionViewLayout
@property (nonatomic, weak) id<MyCustomProtocol> customDataSource;
@end


Next, because the number of items the collection view will manage is relatively low, the custom layout makes use of a caching system to store the layout attributes it generates when preparing the layout and then retrieves these stored values whenever the collection view asks for them. Listing 6-2 shows the three private properties our layout will need to maintain and the init method. The layoutInformation dictionary houses all of the layout attributes for all types of views within our collection view, and the maxNumRows property keeps track of how many rows are needed to populate the tallest column of our tree. The insets object controls spacing between cells and is used in setting the frames for views and the content size. The values for the first two properties are set while preparing the layout, but the insets object should be set using the init method. In this case, INSET_TOPINSET_LEFTINSET_BOTTOM, and INSET_RIGHT refer to constants you define for each parameter.

下一步,因为集合视图中元素的数目实在比较少,自定义布局使用缓存系统来存储布局属性当准备布局的时候再取出存储的值给集合视图。表6-2展示了我们布局需要维持和初始化的3个私有属性。LayoutInformation 字典类型包含了我们集合视图中所有类型的布局属性,maxNumRows 属性跟踪我们需要多少行来填充这个树的所有列。insets 对象控制单元格之间的间距,被用来设置视图的框架和内容大小。牵连个属性会在准备布局的时候设置,但是insets对象应该用在init方法里面。在这里INSET_TOPINSET_LEFTINSET_BOTTOM, and INSET_RIGHT就是给定义的参数。

Listing 6-2  Initializing variables

@interface MyCustomLayout()
 
@property (nonatomic) NSDictionary *layoutInformation;
@property (nonatomic) NSInteger maxNumRows;
@property (nonatomic) UIEdgeInsets insets;
 
@end
 
-(id)init {
    if(self = [super init]) {
        self.insets = UIEdgeInsetsMake(INSET_TOP, INSET_LEFT, INSET_BOTTOM, INSET_RIGHT);
    }
    return self;
}

The last step for this custom layout is to create custom layout attributes. Although this step is not always necessary, in this case, as cells are being placed, the code needs access to the index paths of the current cell’s children so that it can adjust the children cell’s frames to match that of their parent. So subclassing UICollectionViewLayoutAttributes to store an array of the cell’s children provides that information. You subclass UICollectionViewLayoutAttributes, and in the header file add the following code:

自定义布局的最后一步是创建自定义布局。尽管这一步通常不是很必要。在这里例子里面,随着单元格被放置,代码需要访问当前单元格的孩子的索引路径,这样可以调整孩子单元格的frames 来匹配父单元格。子类化UICollectionViewLayoutAttributes类来存储单元格盒子提供的信息到数组里。

@property (nonatomic) NSArray *children;

As explained in the UICollectionViewLayoutAttributes class reference, subclassing layout attributes requires that you override the inherited isEqual: method in iOS 7 and later. For more information on why this is, see UICollectionViewLayoutAttributes Class Reference.

就像UICollectionViewLayoutAttributes 类参考文档里解释的那样,子类化布局属性要求你重写继承的的isEqual:方法(在IOS7或者之后)。更多信息关于这些,请看:UICollectionViewLayoutAttributes Class Reference.

The implementation for the isEqual: method in this case is simple because there’s only one field to compare—the contents of the children array. If the arrays of both layout attributes objects match, then they must be equal because children can belong to only one class. Listing 6-3 shows the implementation for the isEqual: method.

在这个例子中实现isEqual:方法很简单,因为只有一组需要比较——孩子数组的内容。如果两个布局属性对象数组匹配,那么他们必须相等因为孩子只能属于一个类。listig6-3展示了实现isEqual方法。

Listing 6-3  Fulfilling requirements for subclassing layout attributes

-(BOOL)isEqual:(id)object {
    MyCustomAttributes *otherAttributes = (MyCustomAttributes *)object;
    if ([self.children isEqualToArray:otherAttributes.children]) {
        return [super isEqual:object];
    }
    return NO;
}

Remember to include the header file for the custom layout attributes in the custom layout file.

At this point in the process, you are ready to start implementing the main part of the custom layout with the foundations you have laid.

记得在你的自定义布局文件的里面包含自定义布局属性的头文件。
这个过程到这里,你已经完成了你自定义布局中主要部分,你已经奠定好基础了。

Preparing the Layout(准备布局)

Now that all of the necessary components have been initialized, you can prepare the layout. The collection view first calls the prepareLayout method during the layout process. In this example, the prepareLayout method is used to instantiate all of the layout attributes objects for every view in the collection view and then cache those attributes in ourlayoutInformation dictionary for later use. For more information on the prepareLayout method, see Preparing the Layout.

现在所有所有的组件都已经初始化好啦,你可以准备布局了。在布局过程中集合视图首先调用prepareLayout方法。在这个例子中,prepareLayout方法初始化集合视图中所有的布局属性对象,然后把这些属性存储到ourLayoutInformation字典中准备之后的使用。更多关于prepareLayout方法的信息,请看:Preparing the Layout

Creating the Layout Attributes(创建布局属性)

The example implementation of the prepareLayout method is split into two parts. Figure 6-2 shows the goal for the first half of the method. The code iterates over every cell, and if that cell has children, relates those children to the parent cell. As you can see in the figure, this process is done for every cell, including the children cells of other parent cells

这个例子中prepareLayout方法的完成分成两部分。图6-2展示了这个方法第一部分的目标。这些代码在每个单元格重复,同时如果单元格有孩子,把这些孩子和父单元格联系起来。就像图中展示的,包括了孩子单元格和父亲单元格。

Figure 6-2  Connecting parent and child index paths

Listing 6-4 shows the first half of the prepareLayout method’s implementation. Both mutable dictionaries initialized at the beginning of the code form the basis of the caching mechanism. The first, layoutInformation, is the local equivalent of the layoutInformation property. Creating a local mutable copy allows the instance variable to be immutable, which makes sense in the custom layout’s implementation because layout attributes should not be modified after the prepareLayout method finishes running. The code then iterates over each section in increasing order and then over each item within each section to create attributes for every cell. The custom method attributesWithChildrenForIndexPath:returns an instance of the custom layout attributes, with the children property populated with the index paths of the children for the item at the current index path. The attributes object is then stored within the local cellInformation dictionary with its index path as the key. This initial pass over all of the items allows the code to set the children for each item before setting the item’s frame.

listing 6-4 展示了prepareLayout 方法前半部分的实现。代码开始阶段的两个可变字典的初始化构成了缓存机制。首先,layoutInformation属性,是一个本地的layoutInformation属性。创建一个本地的可变拷贝允许实例变量是不可变的,意义在于在你的自定义布局的prepareLayout方法完成之后不允布局属性发生改变。代码之后会按照升序遍历每一节,之后遍历每一节中的元素,给每一个单元格创建属性。自定义方法 返回了一个自定义属性对象的实例,孩子属性是根据现在索引路径找到的元素的孩子。属性对象存在本地的cellInformation 中用索引路径做key。遍历所有元素的初始化允许代码在设置他们的frame之前设置每个元素的孩子。

Listing 6-4  Creating layout attributes

- (void)prepareLayout {
    NSMutableDictionary *layoutInformation = [NSMutableDictionary dictionary];
    NSMutableDictionary *cellInformation = [NSMutableDictionary dictionary];
    NSIndexPath *indexPath;
    NSInteger numSections = [self.collectionView numberOfSections;]
    for(NSInteger section = 0; section < numSections; section++){
        NSInteger numItems = [self.collectionView numberOfItemsInSection:section];
        for(NSInteger item = 0; item < numItems; item++){
            indexPath = [NSIndexPath indexPathForItem:item inSection:section];
            MyCustomAttributes *attributes =
            [self attributesWithChildrenAtIndexPath:indexPath];
            [cellInformation setObject:attributes forKey:indexPath];
        }
    }
    //end of first section

Storing the Layout Attributes(存储布局属性)

Figure 6-3 depicts the process that occurs in the second half of the prepareLayout method in which the tree hierarchy is built from the last row to the first. This approach may at first seem peculiar, but it is a clever way of eliminating the complexity involved with adjusting children cell’s frames. Because the frames of children cells need to match up with those of their parent, and because the amount of space between cells on a row-to-row basis is dependent upon how many children a cell has (including how many children each child cell has and so on), you want to set the child’s frame before setting the parent’s. In this way, the child cell and all of its children cells can be adjusted to match their overall parent’s cell.

图6-3描述了再prepareLayout方法的从最后一行到第一行创建树形结构的第二个过程中发生了什么。这个过程刚开始看起来可能有些奇怪,但是这是一种聪明的避免调整孩子的frame过程中发生混乱的方法。因为孩子单元格要匹配他们的父单元格,同时因为单元格之间排-对-排之间的距离基于一个单元格有多少孩子单元格(包括孩子每个孩子单元格又有多少孩子等),所以你会想在设置父亲之前设置孩子。通过这种方法,孩子单元格和他们所有的孩子都能很好的适应父单元格。 

In step 1, the cells of the last column have been placed in sequential order. In step 2, the layout is determining the frames for the second column. In this column, the cells can be laid out sequentially since no cell has more than one child. However, the green cell’s frame must be adjusted to match that of its parent cell, so it is shifted down one space. In the final step, the cells for the first column are being placed. The first three cells of the second column are the children of the first cell in the first column, so the cell’s following the first cell in the first column are shifted down. In this case, it is not actually necessary to do so since the two cell’s following the first have no children of their own, but the layout object is not smart enough to know this. Rather, it always adjusts the space in case any cell following one with children has children of its own. As well, the green cells have now both shifted down to match that of their parent.

在第一步,最后一列的单元格被按照顺序放置。在步骤2,布局决定了第二列的frame。在这一列,单元格可以按顺序排列因为没有单元格有一个以上的孩子,尽管如此,绿色的单元格必须被设置以适应他的父单元格,所以向下移动一个距离。在最后一个步骤,第一个单元格被放置好了。第二列前三个单元格是第一列第一个单元格的孩子,所以第一列第一个单元格以下的单元格都将下调。在这个例子里面,没有必要对接下来两个单元格这样做,因为他们没有自己的孩子,但是自动布局对象不够聪明明白这些。所以,它会经常调整间距然保证任何一个有孩子的单元格可以调整。同时,绿色的单元格都要向下移动以保证匹配父单元格。

Figure 6-3  The framing process


Listing 6-5 shows the second half of the prepareLayout method, in which the frames for each item are set. The commented numbers following some lines of code correspond to the numbered explanations found after the code.

图6-5 展示了prepareLayout方法的第二部分,设置了每个元素。每行代码后面的数组都有相应的解释在后面

Listing 6-5  Storing layout attributes 存储布局属性

    //continuation of prepareLayout implementation  
    for(NSInteger section = numSections - 1; section >= 0; section—-){
        NSInteger numItems = [self.collectionView numberOfItemsInSection:section];
        NSInteger totalHeight = 0;
        for(NSInteger item = 0; item < numItems; item++){
            indexPath = [NSIndexPath indexPathForItem:item inSection:section];
            MyCustomAttributes *attributes = [cellInfo objectForKey:indexPath]; // 1
            attributes.frame = [self frameForCellAtIndexPath:indexPath
                                withHeight:totalHeight];
            [self adjustFramesOfChildrenAndConnectorsForClassAtIndexPath:indexPath]; // 2
            cellInfo[indexPath] = attributes;
            totalHeight += [self.customDataSource
                            numRowsForClassAndChildrenAtIndexPath:indexPath]; // 3
        }
        if(section == 0){
            self.maxNumRows = totalHeight; // 4
        }
    }
    [layoutInformation setObject:cellInformation forKey:@"MyCellKind"]; // 5
    self.layoutInformation = layoutInformation
}

In Listing 6-5, the code traverses the sections in descending order, building the tree from the back to the front. The totalHeight variable tracks how many rows down the current item needs to be. This implementation does not track spacing smartly, simply leaving empty space below cells with children so that two cell’s children never overlap, and the totalHeightvariable helps accomplish this. The code accomplishes this in the following order:

在lising6-5中,代码按升序遍历了每一节,从后到前创建了树。totalHeight值记录了当前元素需要下移多少。这个实现并不能智能的分析距离,仅仅是简单的间隔单元格之间的距离让单元格的孩子能够不重叠,totalHeight帮助完成这些。代码按照下列顺序完成。

  1. The layout attributes created in our first pass over the data are retrieved from the local dictionary before the cell’s frame is set.

  2. The custom adjustFramesOfChildrenAndConnectorsForClassAtIndexPath: method recursively adjusts the frames of all the cell’s children and grandchildren and so on to match the cell’s frame.

  3. After putting the adjusted attributes back in the dictionary, the totalHeight variable is adjusted to reflect where the next item’s frame needs to be. This is where the code takes advantage of the custom protocol. Whatever object implements that protocol needs to implement the numRowsForClassAndChildrenAtIndexPath: method, which returns how many rows each class needs to occupy given how many children it has.

  4. The maxNumRows property (later needed to set the content size) is set to section 0’s total height. The column with the longest height is always section 0, which has a height adjusted for all of the children in the tree, because this implementation doesn’t include smart space adjusting.

  5. The method concludes by inserting the dictionary with all of the cell attributes into the local layoutInformation dictionary with a unique string identifier as its key.


  1. 布局属性创建了在我们第一次遍历数据的时候,布局属性在设定单元格之前从本地字典取出
  2. 自定义的adjustFramesOfChildrenAndConnectorsForClassAtIndexPath 方法递归的调整单元格孩子孙子的frame来匹配单元格。
  3. 在把调整属性放进字典之后,totalHeight值调整来反映下一个元素的frame是多少。该方法利用了自定义协议。不论什么对象实现了这个协议,都需要实现numRowsForClassAndChildrenAtIndexPath: 方法,这个方法返回了有多少孩子,每个孩子占多少行。
  4. maxNumRows 属性(之后需要设置内容大小)设置了第0节的总高度。列的最高高度总是第0节的高度,这节高度调整树上所有孩子的高度,因为这个实现没有只能调节空间。
  5. 这个方法以把这个单元格的信息插入保存所有单元格信息的layoutInformation属性,用一个唯一的字符串作为key作为结尾。

The string identifier used to insert the dictionary in the final step is used throughout the rest of the custom layout to retrieve the correct attributes for the cells. It becomes even more important when supplementary views come into play further along in the example.

在最后一步插入的标识字符串用于检测其余单元格属性的正确性。当增补视图在这个例子中扮演重要角色的时候它还会变得更加重要。

Providing the Content Size(提供内容大小)

In preparing the layout, the code sets the value of maxNumRows to be the number of rows in the largest section in the layout. This information can be leveraged to set the appropriate content size, which is the next step in the layout process. Listing 6-6 shows the implementation for collectionViewContentSize. It relies on the constants ITEM_WIDTH andITEM_HEIGHT, which are presumably global to the application (for instance, they are needed in the custom cell implementation to size the cell label correctly).

在准备布局过程中,代码设置了maxnumRows的最大值作为布局中这一节中的最大行数。作为布局过程中的下一步,这个信息可以作为设置合适内容大小的杠杆。listing6-6  展示了collectionViewContentSize 是如何实现的。它以来于ITEM_WIDTH 和 ITEM_HEIGHT,这是全局的应用(比如,他们需要在自定义单元格里面正确的实现单元格的标签)

Listing 6-6  Sizing the content area

- (CGSize)collectionViewContentSize {
    CGFloat width = self.collectionView.numberOfSections * (ITEM_WIDTH + self.insets.left + self.insets.right);
    CGFloat height = self.maxNumRows * (ITEM_HEIGHT + _insets.top + _insets.bottom);
    return CGSizeMake(width, height);
}

Providing Layout Attributes(提供布局属性)

With all of the layout attributes objects initialized and cached, the code is fully prepared to provide all of the layout information requested in thelayoutAttributesForElementsInRect: method. This method is the second step in the layout process, and unlike the prepareLayout method, it is required. The method provides a rectangle and expects an array of layout attributes objects for any views contained within the provided rectangle. In some cases, collection views that house thousands of items might wait until this method is called to initialize layout attributes objects for only the elements contained within the provided rectangle, but this implementation relies on caching instead. Therefore, the layoutAttributesForElementsInRect: method simply requires looping over all of the stored attributes and gathering them into a single array returned to the caller.

随着所有布局属性对象初始化并缓存,代码已经完全准备好提供 函数:layoutAttributesForElementsInRect: 要求的信息。这个方法是布局过程的第二步,和prepareLayout方法不同,这个方法要求必须实现。这个方法提供了一个矩形,并要求一个提供矩形内包含的视图的布局属性对象的数组。在一些情况下,包含上千元素的集合视图会等到调用这个方法调用,来初始化提供的矩形内的视图的布局属性对象,但是这个实现依赖于缓存。因此layoutAttributesForElementsInRect方法仅仅需要遍历所有的缓存属性,并采集他们返回给调用者。

Listing 6-7 shows the implementation of the layoutAttributesForElementsInRect: method. The code traverses all of the subdictionaries that contain layout attributes objects for specific types of views within the main dictionary _layoutInformation. If the attributes examined within the subdictionary are contained within the given rectangle, they’re added to an array storing all of the attributes within that rectangle, which is returned after all of the stored attributes have been checked.

listing 6-7 展现了layoutAttributesForElementsInRect 方法的实现。代码遍历了主字典_layoutInformation包含的特殊类型的布局属性对象的所有子字典。如果子字典的属性检查出包含在给的矩形中,把它们添加到一个存储矩形所有属性的数组中,在所有存储属性检查过之后返回这个数组。

Listing 6-7  Collecting and processing stored attributes

- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
    NSMutableArray *myAttributes [NSMutableArray arrayWithCapacity:self.layoutInformation.count];
    for(NSString *key in self.layoutInformation){
        NSDictionary *attributesDict = [self.layoutInformation objectForKey:key];
        for(NSIndexPath *key in attributesDict){
            UICollectionViewLayoutAttributes *attributes =
            [attributesDict objectForKey:key];
            if(CGRectIntersectsRect(rect, attributes.frame)){
                [attributes addObject:attributes];
            }
        }
    }
    return myAttributes;
}

Note: The implementation for layoutAttributesForElementsInRect: never references whether or not the view for a given attributes is visible. Remember that the rectangle provided by this method is not necessarily going to be the visible rectangle and that, no matter what your implementation is, it should never assume the attributes it is returning are for visible views. For a lengthier discussion about the layoutAttributesForElementsInRect: method, see Providing Layout Attributes for Items in a Given Rectangle.


注意:layoutAttributesForElementsInRect方法的实现和视图的属性是否可视无关。记住这个方法提供的矩形不是必须加入到可视矩形,不管你实现的是什么,都不应该假定属性是为可视视图返回的。关于layoutAttributesForElementsInRect方法更深入的讨论,请看 Providing Layout Attributes for Items in a Given Rectangle.

Providing Individual Attributes When Requested(当请求时提供个别的属性)

As discussed in the section Providing Layout Attributes On Demand, the layout object must be prepared to return layout attributes for any singular item of any kind of view within your collection view at any time once the layout process is complete. There are methods for all three kinds of views—cells, supplementary views and decoration views—but the app currently uses cells exclusively, so the only method that requires an implementation for the time being is the layoutAttributesForItemAtIndexPath: method.

就像在Providing Layout Attributes On Demand节中讨论的那样,布局对象在布局过程中的任何时间,必须准备为你的集合视图中的任何类型的单独的元素提供布局属性。这里有所有视图(单元格,增补视图,装饰视图)对应的方法,但是呢现在我们的应用只用到了单元格,所以现在只要实现layoutAttributesForItemAtIndexPath: 方法就好了。

Listing 6-8 shows the implementation for this method. It taps into the stored dictionary for cells, and within that subdictionary, it returns the attributes object stored with the specified index path as its key.

listing 6-8 展现了这个方法的实现,这个方法从存储单元格的字典里获取,通过这个子字典,返回了根据特定key的属性对象

Listing 6-8  Providing attributes for specific items

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    return self.layoutInfo[@"MyCellKind"][indexPath];
}

Figure 6-4 shows what the layout looks like at this point in the code. All of the cells have been placed and adjusted correctly to match their parents, but the lines connecting them haven’t been drawn yet.

图 6-4 展现了代码到此展现的布局。所有单元格都已经放在正确位置并且正确调整来匹配父对象,但是他们之间的连线还没画。

Figure 6-4  The layout so far
未完待续……

本文原创,转载请注明出处:http://blog.csdn.net/zhenggaoxing/article/details/44302025




未完待续
本文原创,转载请注明出处:http://blog.csdn.net/zhenggaoxing/article/details/44301
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值