Note: I am a translator, and add a bit of my opinion.
Objective
Uicollectionview was introduced for the first time in IOS6, and is also a star in the UIKit view class. It shares a set of API designs with UITableView, but also has some extensions on the UITableView. Uicollectionview's most powerful and significantly more than UITableView feature is its fully flexible layout structure. In this article, we will implement a fairly complex custom collection view layout and, by the way, discuss an important part of this class design. The sample code for the project is on GitHub.
Directory
1. Layout objects (layouts Objects)
2. Cells and other views
3. Custom Layouts
4. Animations
5. Conclusion
6. Extended Reading
1. Layout objects (layouts Objects)
UITableView and Uicollectionview are both Data-source and delegate-driven. They play only the container role () during the display of their child view sets dumb containers
, and the true content of the child view set is not known.
UICollectionView
Further abstraction is made on top of this. It delegates control over the location, size, and appearance of its child views to a separate layout object. By providing a custom layout object, you can implement almost any layout you can imagine. Layouts inherit from the UICollectionViewLayout
abstract base class. In IOS6, UICollectionViewFlowLayout
a concrete layout implementation is presented in the form of class.
We can use flow layout to implement a standard grid view, which is probably the most common use case in collection view. Although most people think so, Apple is smart enough not to name the class explicitly, and UICollectionViewGridLayout
uses a more generic term flow layout to better describe the functionality of the class: it creates its own layout by placing cells one after another, and when needed, Inserts a column break for horizontal or vertical rows. By customizing the scrolling direction, the size and spacing between cells, flow layout can also lay out cells in a single row or single column. In fact, UITableView
the layout can be imagined as a special case of flow layout.
Before you prepare yourself to write a UICollectionViewLayout
subclass, you need to ask yourself if you can use UICollectionViewFlowLayout
the layout that implements your mind. This class is easy to customize and can be further customized by inheriting itself. Interested to see this article.
2. Cells and other views
To accommodate any layout, collection view creates a similar, but more flexible, viewing level (view hierarchy) than table view. As usual, your main content is displayed in the cell, and the cell can be arbitrarily grouped into a section. The cell of the Collection view must be a UICollectionViewCell
subclass. In addition to the cell,collection view, there are two additional views that are managed: supplementary and decoration.
The supplementary views in collection view correspond to the section header and Footer views of table view. Like cells, their content is driven by data source objects. However, unlike the usage in Table view, supplementary view does not necessarily serve as header or footer view, and their number and placement positions are entirely controlled by the layout.
decoration views purely as an ornament. They belong entirely to the layout object and are managed by the layout object, and they do not get the contents from the data source. When the layout object specifies that a decoration view is required, collection view is automatically created and the layout parameters provided by the layout object are applied to it. There is no need to prepare any content for the custom view.
Supplementary views and decoration views must be a subclass of Uicollectionreusableview. Each view class used by the layout needs to be registered in the collection view, so that when data source makes them dequeue from the reuse pool, they will be able to create new instances. If you are using Interface Builder, you can complete the cell registration in collection view by dragging a cell to collection view in the Visual editor. The same method can be used on supplementary view, if you use it UICollectionViewFlowLayout
. If not, you can only registerClass:
registerNib:
manually register the view class by calling or using the method. You need to viewDidLoad
do these operations in.
3. Custom Layouts
As an example of a very meaningful custom collection view layout, we might as well envision a weekly (week) view of a typical calendar application. The calendar is displayed one week at a time, and each day of the week is displayed in the column. Each calendar event will be displayed as a cell in our collection view, with the location and size representing the event start date time and duration.
There are generally two types of collection view layouts:
1. Content-independent layout calculations . This is exactly what you know about things like UITableView and uicollectionviewflowlayout. The location and appearance of each cell is not based on what it displays, but the order in which all cells are displayed is based on the order of content. The default flow layout can be used as an example. Each cell is placed based on the previous cell (or, if there is not enough space, starts from the next line). Layout objects do not have to access the actual data to calculate the layout.
2. Content-based layout calculations . Our calendar view is an example of this type. In order to calculate the start and end time of the display event, the layout object needs direct access to the collection view's data source. In many cases, the layout object needs to take out not only the data of the currently visible cell, but also the data from all records that determine which cells are currently visible.
In our calendar example, if the layout object accesses the properties of cells within a rectangle, it must iterate over all the events provided by the data source to determine which are in the required time window. This is enough to calculate the index paths of the cell within a rectangle (assuming that all cells in the grid are the same size) compared to some relatively simple flow layout that is calculated independently of the data source.
If you have a content-dependent layout, it implies that you need to write a custom layout class and not use a custom one UICollectionViewFlowLayout
, so that's exactly what we need to do.
The Uicollectionviewlayout documentation lists the methods that subclasses need to override.
1) collectionviewcontentsize
Since collection view has no knowledge of its content, the first information that the layout provides is the size of the scrolling area so that collection view can manage scrolling correctly. The layout object must calculate the total size of its contents at this time, including supplementary views and decoration views. Note that although most classic collection view limits scroll in one axis (as in the UICollectionViewFlowLayout
same way), this is not required.
In our calendar example, we want the view to scroll vertically. For example, if we want to take up 100 points in vertical space for one hours, this shows that the content height of the whole day is 2,400 points. Note that we are not able to scroll horizontally, which means that we collection view can only be displayed for one week. In order to be able to page through multiple stars during the calendar, we can use multiple collection view (one for a week) in a standalone (paginated) scroll view (can use Uipageviewcontroller), or stick with a Collection view and returns a large enough content width, which makes the user feel free to swipe in two directions.
- (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); = Cgsizemake (contentwidth, contentheight); return contentsize;}
For the sake of clarity, I chose the layout on a very simple model: assuming that every Sunday is the same, every day is the same, which means that the number of days is 0-6. In a real-world calendar program, the layout will use a large number of dates based on its calculations NSCalendaar
.
2) Layoutattributesforelementsinrect:
This is the most important method in any layout class, and it may also be the easiest way to confuse it. Collection view calls this method and passes a rectangle in its own coordinate system past. This rectangle represents the visible rectangular area of the view (that is, its bounds), and you need to be prepared to handle any rectangles that are passed to you.
Your implementation must return an array of contained UICollectionViewLayoutAttributes
objects, for each cell containing one such object, supplementary view or decoration view is visible within the rectangular area. The UICollectionViewLayoutAttributes
collection class contains all the related layout properties for item within the view. By default, this class contains,,,, frame
center
size
transform3D
alpha
, zIndex
and hidden
attributes. If your layout wants to control the properties of other views (such as the background color), you can create a UICollectionViewLayoutAttributes
subclass and add your own attributes.
Layout Property objects (Layouts attributes objects) indexPath
are associated with properties and their corresponding cell,supplementary view or decoration view. Collection view after you request from a layout object to a layout property for all items, it instantiates all views and applies the corresponding properties to each view.
Attention! This approach involves all types of views, that is, cell,supplementary and decoration. A naïve implementation might choose to ignore the incoming rectangle and return layout properties for all views in the collection view. This is an effective method in the prototyping and development layout phase. However, this will have a very bad effect on performance, especially when the cell is far less than the number of cells, collection view and layout objects will do extra work for those views that are not visible.
Your implementation needs to do these steps:
Create an empty mutable array to hold all the layout properties.
Determines which cells in the index paths frame are completely or partially in the rectangle. This calculation requires you to extract the data you need to display from the collection view's data source. Then call the method you implement in the loop layoutAttributesForItemAtIndexPath:
to create and configure an appropriate layout Property object for each index path, and add each object to the array.
If your layout contains supplementary views, the index paths of the supplementary view is visible within the calculated rectangle. Calls you to implement in the loop layoutAttributesForSupplementaryViewOfKind:atIndexPath:
, and adds these objects to the array. By passing the different characters you choose for the kind parameter, you can distinguish between different kinds of supplementary views (such as headers and footers). When you need to create a view, collection view Nginx the kind word back to your data source. Remember that the number and type of views of supplementary and decoration are completely controlled by the layout. You are not subject to headers and footers restrictions.
If the layout contains decoration views, the calculation rectangle is visible within the decoration views of the index paths. Calls you to implement in the loop layoutAttributesForDecorationViewOfKind:atIndexPath:
, and adds these objects to the array.
Returns an array.
Our custom layouts do not use decoration views, but two supplementary views (column headers and row headers) are used:
-(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 *indexpathinchvisibleindexpaths) {Uicollectionviewlayoutattributes*attributes =[self layoutattributesforitematindexpath:indexpath]; [Layoutattributes addobject:attributes]; } //Supplementary viewsNsarray *dayheaderviewindexpaths =[self indexpathsofdayheaderviewsinrect:rect]; for(Nsindexpath *indexpathinchdayheaderviewindexpaths) {Uicollectionviewlayoutattributes*attributes =[self layoutattributesforsupplementaryviewofkind:@"Dayheaderview"Atindexpath:indexpath]; [Layoutattributes addobject:attributes]; } Nsarray*hourheaderviewindexpaths =[self indexpathsofhourheaderviewsinrect:rect]; for(Nsindexpath *indexpathinchhourheaderviewindexpaths) {Uicollectionviewlayoutattributes*attributes =[self layoutattributesforsupplementaryviewofkind:@"Hourheaderview"Atindexpath:indexpath]; [Layoutattributes addobject:attributes]; } returnlayoutattributes;}
3) Layoutattributesfor ... Indexpath
Sometimes, collection view requests layout properties for a particular cell,supplementary or decoration view to layout objects, not all visible objects. This is when the other three methods begin to work, you layoutAttributesForItemAtIndexPath:
need to create and return a separate layout Property object so that you can properly format the cell that corresponds to your index path.
You can +[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:]
modify the properties by calling this method and then depending on the index path. In order to get the data that needs to be displayed in this index path, you may need to access the collection view's data source. So far, be sure to set the frame property at least unless all of your cells are located above each other.
-(Uicollectionviewlayoutattributes *) Layoutattributesforitematindexpath: (Nsindexpath * ) indexpath{calendardatasource *datasource = Self.collectionView.dataSource; id 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 properties of layout parameters instead of working with constraints, but this is exactly what Uicollectionviewlayout does. Although you might use automatic layout to define the collection view frame and the layout of each cell inside it, the frames of cells still needs to be computed in an old-fashioned way.
Similarly, layoutAttributesForSupplementaryViewOfKind:atIndexPath:
and layoutAttributesForDecorationViewOfKind:atIndexPath:
methods need to do the same thing for supplementary and decoration views respectively. You only need to implement these two methods if your layout contains such a view. UICollectionViewLayoutAttributes
contains two additional factory methods, +layoutAttributesForSupplementaryViewOfKind:withIndexPath:
and +layoutAttributesForDecorationViewOfKind:withIndexPath:
, using them to create the correct layout Property object.
4) Shouldinvalidatelayoutforboundschange:
Finally, when the bounds of collection view changes, the layout needs to tell collection view whether the layout needs to be recalculated. My guess is this: When collection view changes size, most layouts are invalidated, such as when the device rotates. Therefore, a naïve implementation may simply return YES. While it is important to implement functionality, the bounds of the scroll view changes when scrolling, which means that your layout is discarded multiple times per second. Judging by the complexity of the calculations, this will have a significant impact on performance.
When the width of the collection view changes, our custom layouts must be discarded, but this scrolling does not affect the layout. Fortunately, collection view passes its new bounds to the shouldInvalidateLayoutForBoundsChange:
method. This allows us to compare the current bounds of the view with the new bounds to determine the return value:
- (BOOL) Shouldinvalidatelayoutforboundschange: (cgrect) newbounds{ = self.collectionView.bounds; if (Cgrectgetwidth (newbounds)! = Cgrectgetwidth (oldbounds)) {return YES ; } return NO;}
4. Animations
1) Insert and delete
The cell in UITableView comes with a very nice set of insert and delete animations. However, when adding and removing cell definition animations for Uicollectionview, the UIKit engineer encounters the problem: if the layout of collection view is completely variable, pre-defined animations will not be able to blend well with the developer's custom layouts. They come up with an elegant approach: when a cell (or supplementary or decoration view) is inserted into collection view, collection view not only requests layout properties in the cell's normal state to its layout , and also requests its initial layout properties, such as the cell that needs to be inserted at the beginning of the animation. Collection view will simply create a animation block and, in this block, change the properties of all cells from the initial (initial) state to the normal (normal).
By providing different initial layout properties, you can fully customize the Insert animation. For example, setting an initial alpha of 0 will result in a fade-in animation. Setting a pan and zoom at the same time will produce the effect of moving the zoom.
The same principle applies to the deletion, this time the animation is from the normal to a series of final layout properties you set. These are the methods you need to implement in the layout class for initial or final layout parameters.
Initiallayoutattributesforappearingitematindexpath:
Initiallayoutattributesforappearingsupplementaryelementofkind:atindexpath:
Initiallayoutattributesforappearingdecorationelementofkind:atindexpath:
Finallayoutattributesfordisappearingitematindexpath:
Finallayoutattributesfordisappearingsupplementaryelementofkind:atindexpath:
Finallayoutattributesfordisappearingdecorationelementofkind:atindexpath:
2) switching between layouts
A collection view layout can be dynamically switched to another layout in a similar way. When a message is sent setCollectionViewLayout:animated:
, collection view queries the cells for new layout parameters in the new layout, and then dynamically transforms each cell (the same cell in the old and new layout through index path) from the old parameter to the new layout parameter. You don't have to do anything.
5. Conclusion
Depending on the complexity of the custom collection view layout, it is often not easy to write one. In essence, this is as difficult as writing a full implementation of the same layout custom view class from scratch. Because the calculations involved need to determine which child views are currently visible, and where they are. However, the use UICollectionView
still gives you some good results, such as cell reuse, auto-support animations, not to mention neat standalone layouts, sub-view management, and data-supply architecture rules (preparation its architecture Prescribes.).
Customizing the collection View layout is also a good step for a lightweight view controller, just as your view controller does not include any layout code. As explained in Chris ' article, combining all this with a separate DataSource class, collection View controller will be hard to contain any more code.
Whenever I use UICollectionView
it, I am impressed by its simplicity of design. For an experienced Apple engineer, in order to come up with such a flexible class, it is likely to need to first consider NSTableView
and UITableView
.
6. Extended Reading
- Collection View Programming Guide.
- Nshipster on
UICollectionView
.
UICollectionView
: The Complete Guide, e-book by Ash furrow.
MSCollectionViewCalendarLayout
By Eric Horacek are an excellent and more complete implementation of a custom layout for a week calendar view.
IOS UIView 03-Customizing the Collection View layout