IOS series translations: custom Collection View Layout

Source: Internet
Author: User

Source: Ole begann Source: huang aiwu (@ answer-huang ). Welcome to the technical translation team.

UICollectionView is introduced for the first time in iOS6 and is a new star in the UIKit View class. It shares the API design with UITableView, but also makes some extensions on UITableView. The most powerful and significantly larger than UITableView of UICollectionView is its fully flexible layout structure. In this article, we will implement a very complex custom collection view layout and, by the way, discuss the important part of this class design. The sample code of the project is on GitHub.

Layout object

Both UITableView and UICollectionView are driven by data-source and delegate. They acted as dumb containers for the sub-Visual Gallery they showed and were unaware of their real content (contents.

UICollectionView is further abstracted. It delegates control over the position, size, and appearance of its subviews to a separate layout object. By providing a custom Layout object, you can implement almost any layout you can imagine. The layout inherits from the abstract base class UICollectionViewLayout. IOS6 presents a specific layout implementation in the form of the UICollectionViewFlowLayout class.

Flow layout can be used to implement a standard grid view, which may be the most common use case in collection view. Although most people think so, Apple is very smart and does not explicitly name this class as UICollectionViewGridLayout. The more general term flow layout is used to better describe the ability of this class: it places cells one by one to build its own layout. When needed, insert a column break in the horizontal or vertical bar. By customizing the scroll direction and the spacing between the size and cell, flow layout can also layout cells in a single row or single column. In fact, the layout of UITableView can be considered a special case of flow layout.

Before you prepare to write a subclass of UICollectionViewLayout, you need to ask yourself whether you can use UICollectionViewFlowLayout to implement your own layout. This class is easy to customize and can inherit from itself for further customization. Read this article if you are interested.

Cells and other Views

To adapt to any layout, collection view creates a similar view level (view hierarchy) that is more flexible than table view ). As usual, your main content is displayed in the cell, and the cell can be grouped into any section. The cells of the Collection view must be a subclass of UICollectionViewCell. In addition to cells, collection view manages two additional views: supplementary views and decoration views.

Supplementary views in collection view are equivalent to section header and footer views in table view. Like cells, their content is driven by data source objects. However, unlike table view, supplementary view is not necessarily used as a header or footer view; their quantity and placement are completely controlled by the layout.

Decoration views is purely an ornament. They are completely layout objects and managed by layout objects. They do not get their contents from the data source. When a decoration view is specified for a layout object, the collection view is automatically created and provides layout parameters for its application layout object. You do not need to prepare any custom View content.

Supplementary views and decoration views must be subclasses of UICollectionResuableView. Each view class you use in the layout needs to be registered in the collection view, so that data source can create a new instance only when it asks him to columns from the reuse pool. If you are using the Interface Builder, you can drag a cell in the visual editor to the collection view to complete cell registration in the collection view. The same method can also be used on supplementary view, provided that you use UICollectionViewFlowLayout. If not, you can only manually register the View class by calling registerClass: Or registerNib: method. You need to perform these operations in viewDidLoad.

Custom Layout

As an example of a very meaningful custom collection view layout, we may imagine a week view in a typical calendar application. The calendar displays one week at a time, and each day of the week is displayed in the column. Each calendar event is displayed in a cell in our collection view. The position and size indicate the start date and duration of the event.

There are two types of collection view layout:

1. Independent of content layout calculation. This is exactly what you know, such as UITableView and UICollectionViewFlowLayout. The position and appearance of each cell are not based on the displayed content, but the display sequence of all cells is based on the content sequence. The default flow layout can be used as an example. Each cell is placed based on the previous cell (or starts from the next row if there is not enough space ). The layout object does not have to access the actual data to calculate the layout.

2. Content-based layout calculation. The calendar view is an example of this type. To calculate the start time and end time of the display event, the layout object needs to directly access the data source of the collection view. In many cases, layout objects not only need to retrieve the data of the currently visible cell, but also need to retrieve some visible data from all records to determine which cells are currently visible.

In our calendar example, if a layout object accesses the properties of cells in a rectangle, it must iterate all the events provided by the data source to determine which time windows are located in the required time window. Compared with flow layout, which is relatively simple and independently calculated by the data source, this is enough to calculate the index paths of the cell in a rectangle (assuming the size of all cells in the grid is the same ).

If there is a dependency content layout, it means you need to write a custom Layout class, and you cannot use the custom UICollectionViewFlowLayout. So this is exactly what we need to do.

The document of UICollectionViewLayout lists the methods to be rewritten by subclass.

CollectionViewContentSize

Because the collection view does not know its content, the first information to be provided in the layout is the size of the rolling area, so that the collection view can correctly manage the scrolling. The layout object must calculate the total size of its content at this time, including supplementary views and decoration views. Note that although most classic collection views are limited to scroll in one axis (just like UICollectionViewFlowLayout), this is not necessary.

In our calendar example, we want the view to scroll vertically. For example, if we want to occupy 100 points for an hour in the vertical space, the height of the content displayed for a whole day is 2400 points. Note that we cannot scroll horizontally, which means that our collection view can only display for one week. To enable pagination between multiple weeks in the calendar, we can use multiple collection views (one week) in an independent (paging) scroll view (UIPageViewController can be used ), or stick to a collection view and return a sufficiently large content width, which makes the user feel free to slide in two directions.

1234567891011121314151617 - (CGSize)collectionViewContentSize { // Don't scroll horizontally CGFloat contentWidth = self.collectionView.bounds.size.width; // Scroll vertically to display a full day CGFloat contentHeight = DayHeaderHeight + (HeightPerHour * HoursPerDay); CGSize contentSize = CGSizeMake(contentWidth, contentHeight); return contentSize; }

For clarity, I chose to layout on a very simple model: assuming that the number of days per week is the same, and the length of each day is the same,

That is to say, the number of days is 0-6. In a real calendar program, la s will greatly use NSCalendar-based dates for their own calculations.

LayoutAttributesForElementsInRect:

This is the most important and confusing method in any layout class. Collection view calls this method and passes a rectangle in its own coordinate system. This rectangle represents the visible rectangle area of this view (that is, its bounds). You need to prepare any rectangle that is passed to you.

Your implementation must return an array containing the UICollectionViewLayoutAttributes object. Each cell contains such an object. supplementary view or decoration view is visible in the rectangle. The UICollectionViewLayoutAttributes class contains all the related layout attributes of items in the collection view. By default, this class includes frame, center, size, transform3D, alpha, zIndex attributes (properties), and hidden attributes (attributes ). If your layout wants to control attributes of other views (such as the background color), you can create a subclass of UICollectionViewLayoutAttributes and add your own attributes.

Layout attribute objects are associated with their corresponding cell, supplementary view, or decoration view through the indexPath attribute. After the collection view is requested by all items from the layout object to the layout attribute, it will instantiate all views and apply the corresponding attributes to each view.

Note! This method involves all types of views, namely cell, supplementary views and decoration views. A naive implementation may ignore the passed rectangle and return layout attributes for all views in the collection view. This is an effective method in the prototype design and development layout phase. However, this will have a very bad impact on performance, especially when the number of cells is much smaller than the total number of cells, collection view and layout object will do additional unnecessary work for those invisible views.

You need to perform the following steps:

1. Create an empty mutable array to store all layout attributes.

2. Determine which cells in index paths have full or partial frames in the rectangle. This computation requires you to retrieve the data you want to display from the data source of the collection view. Then you can call the layoutAttributesForItemAtIndexPath method in the loop to create and configure an appropriate layout attribute object for each index path, and add each object to the array.

3. If your layout contains supplementary views, calculate the index paths of the supplementary view in the rectangle. Call your implemented layoutAttributesForSupplementaryViewOfKind: atIndexPath: in the loop and add these objects to the array. By passing different characters you choose for the kind parameter, You can differentiate different types of supplementary views (such as headers and footers ). When you need to create a view, the collection view will pass the kind character back to your data source. Remember that the number and type of supplementary and decoration views are completely controlled by the layout. You are not restricted by headers and footers.

4. If the layout contains decoration views, calculate the index paths of decoration views in the rectangle. Call your implemented layoutAttributesForDecorationViewOfKind: atIndexPath: in a loop and add these objects to the array.

5. returns an array.

Decoration views is not used for our custom layout, but two types of supplementary views (column headers and row headers) are used)

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *layoutAttributes = [NSMutableArray array]; // Cells // We call a custom helper method -indexPathsOfItemsInRect: here // which computes the index paths of the cells that should be included // in rect. NSArray *visibleIndexPaths = [self indexPathsOfItemsInRect:rect]; for (NSIndexPath *indexPath in visibleIndexPaths) { UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; [layoutAttributes addObject:attributes]; } // Supplementary views NSArray *dayHeaderViewIndexPaths = [self indexPathsOfDayHeaderViewsInRect:rect]; for (NSIndexPath *indexPath in dayHeaderViewIndexPaths) { UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:@"DayHeaderView" atIndexPath:indexPath]; [layoutAttributes addObject:attributes]; } NSArray *hourHeaderViewIndexPaths = [self indexPathsOfHourHeaderViewsInRect:rect]; for (NSIndexPath *indexPath in hourHeaderViewIndexPaths) { UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:@"HourHeaderView" atIndexPath:indexPath]; [layoutAttributes addObject:attributes]; } return layoutAttributes; }

Sometimes, collection view requests layout properties from a special cell, supplementary, or decoration view to the layout object, rather than all visible objects. This is the layoutAttributesForItemAtIndexPath that you implement when the other three methods start to take effect. You need to create and return a separate layout attribute object, in this way, the cell corresponding to your index path can be correctly formatted.

You can call the + [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:] method to modify the attributes based on the index path. To obtain the data to be displayed in the index path, you may need to access the data source of the collection view. So far, at least make sure that the frame attribute is set, unless all your cells are located above each other.

1234567891011121314151617 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { CalendarDataSource *dataSource = self.collectionView.dataSource; id<CalendarEvent> event = [dataSource eventAtIndexPath:indexPath]; UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; attributes.frame = [self frameForEvent:event]; return attributes; }

If you are using automatic layout, you may be surprised that we are directly modifying the frame attribute of the layout parameter, rather than working with the constraint, but this is exactly the work of UICollectionViewLayout. Although you may use an automatic layout to define the frame of the collection view and the layout of each cell in the collection view, frames of cells still needs to be calculated in an old-fashioned way.

Similarly, layoutAttributesForSupplementaryViewOfKind: atIndexPath: And layoutAttributesForDecorationViewOfKind: atIndexPath: The methods must be supplementary and decoration views respectively. You need to implement these two methods only when your layout contains such views. UICollectionViewLayoutAttributes contains two other factory methods: + layoutAttributesForSupplementaryViewOfKind: withIndexPath: And + layoutAttributesForDecorationViewOfKind: withIndexPath:, which are used to create the correct layout attribute objects.

ShouldInvalidateLayoutForBoundsChange:

Finally, when the bounds of the collection view changes, the layout must tell the collection view whether to recalculate the layout. My guess is: when the collection view changes in size, most la s will be voided, such as when the device rotates. Therefore, a naive implementation may simply return YES. Although the implementation function is very important, the bounds of the scroll view will also change during scrolling, which means that your layout will be discarded multiple times per second. Based on the computing complexity, this will have a great impact on the performance.

When the width of the collection view changes, our custom layout must be discarded, but this scrolling will not affect the layout. Fortunately, collection view transmits its new bounds to shouldInvalidateLayoutForBoundsChange: method. In this way, we can compare the current bounds and the New bounds of the view to determine the return value:

123456789101112131415 - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds  {  CGRect oldBounds = self.collectionView.bounds;  if (CGRectGetWidth(newBounds) != CGRectGetWidth(oldBounds)) {  return YES;  }  return NO;  }
Animation

Insert and delete

Cell in UITableView comes with a set of very beautiful insert and delete animations. However, when the cell animation function is added or deleted for UICollectionView, The UIKit engineer encounters the following problem: If the layout of the collection view is completely variable, therefore, pre-defined animations cannot be well integrated with developer-defined la S. They proposed an elegant method: When a cell (or supplementary or decoration view) is inserted into the collection view, the collection view not only requests the layout attribute in the normal state of the cell to its layout, meanwhile, the initial layout attribute is also requested. For example, an animation cell needs to be inserted at the beginning. Collection view creates an animation block and changes the attributes of all cells from the initial state to normal state ).

By providing different initial layout attributes, you can completely customize the insert animation. For example, if the initial alpha is set to 0, a light animation is generated. You can also set a translation and scaling function to achieve the moving and scaling effect.

The same principle applies to deletion. This animation is from the normal state to a series of final layout attributes you set. These are the methods you need to implement the initial or final layout parameters in the layout class.

InitialLayoutAttributesForAppearingitemAtIndexPath:

InitialLayoutAttributesForAppearingSupplementaryElementOfKind: atIndexPath:

InitialLayoutAttributesForAppearingDecorationElementOfKind: atIndexPath:

FinalLayoutAttributesForDisappearingItemAtIndexPath:

FinalLayoutAttributesForDisappearingSupplementaryElementOfKind: atIndexPath:

FinalLayoutAttributesForDisappearingDecorationElementOfKind: atIndexPath:

Inter-layout Switching

You can dynamically switch the layout of a collection view to another layout in a similar way. When a setCollectionViewLayout: animated: message is sent, collection view queries the new layout parameters for cells in the new layout, then, each cell is dynamically transformed from the old parameter to the new layout parameter (the same cell is determined by the index path in the new and old la S. You don't need to do anything.

Conclusion

According to the complexity of the custom collection view layout, it is usually not easy to write a collection view. Specifically, this is essentially just as difficult as writing a complete custom View class from the ground up to implement the same layout. Because the involved computing needs to determine which subviews are currently visible and their locations. Even so, the use of UICollectionView brings you some good effects, such as cell reuse, automatic support for animation, not to mention the clean and independent layout, subview management, and data provision architecture (data preparation its architecture prescribes .).

Custom collection view layout is also a good step towards lightweight view controller, just as your view controller does not contain any layout code. As Chris explained in his article, combining all this with an independent datasource class makes it difficult for the view Controller of the collection view to include any code.

Every time I use UICollectionView, I am overwhelmed by its concise design. For an experienced Apple engineer, to come up with such a flexible class, it is likely that NSTableView and UITableView should be considered first.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.