preface
UITableView and UICollectionView are the most commonly used controls by our developers. A large number of streaming layouts require these two controls, so these two controls are also Apple’s key optimization objects. In past WWDC, we have benefited from UITableViewDataSourcePrefetching, optimized version Autolayout bring performance improvement, and the resulting UITableViewDragDelegate native drag and drop functionality. This year, Apple is bringing a new Compositional Layout. It will completely overturn the UICollectionView layout experience, greatly expand the plasticity of UICollectionView.
background
Early App design is relatively simple, using UICollectionViewFlowLayout can handle most usage scenarios. But with the development of the application, more and more pages tend to be complicated, UICollectionViewFlowLayout in the face of the complex layout tend to be ragged, or is very complex, involves a lot of calculation and judgment. And the freedom of higher UICollectionViewLayout has a higher access threshold, a little careless also prone to a variety of bugs.
Item
introduce
Compositional Layout is the new UICollectionView Layout released with iOS 13. Its goals are threefold:
- Composable
- Flexible Flexible
- Fast Fast
To achieve these three goals, Compositional Layout adds a layer of Group concept on the basis of the original UICollectionViewLayout Item Section. Multiple items make up a Group and multiple groups make up a Section.
With all that said, it might as well be code
// Create a List by Specifying Three Core Components: Item, Group and Section
let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(44.0))
let item = NSCollectionLayoutItem(layoutSize: size)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
Copy the code
As you can see, in order to be able to describe the complex layout, we need to create multiple classes to describe the size, spacing, and spacing of Item, Group, and Section, respectively.
How to interpret the above code?
- First of all,
Item
The height is 44 and the width is the parent view (Group
) 100% of the width. Group
The size description uses andItem
Exactly the same size, i.e. the height is 44 and the width is the superview (Section
) 100% of the width.Section
The width of is the width of the UICollectionView, and the height defaults to thatGroup
The total height of all elements rendered.- Finally, we will size the UICollectionView via Frame or AutoLayout.
Can you picture in your mind what this UICollectionView looks like? Well, I can’t either, but the good thing is THAT I can run through the code and see the actual results.
Okay, I’ll admit it’s a little hard. Since we look at the code from the top down, if the Compositional Layout hierarchy’s size depends on the superview, we have to combine the superview with our own Layout to create the final Layout, which requires a bit of imagination.
In this example, each “UITableViewCell” is an Item and a Group, and the entire “UITableViewCell” contains only one Section.
So you must wonder, why do we need a Group? Please be patient. The answer to this question needs to be seen until the end.
The core layout
Let’s start with the basic core layout. Before getting into the details of the four main classes used in Compositional Layout, we need to look at a new class for describing size.
NSCollectionLayoutDimension
In the past, we could use CGSize to describe a fixed-size Item. Later, we have estimatedItemSize to describe an Item that is dynamically sized and give it an estimated value. More often, however, to accommodate different screen sizes, we need to manually calculate the size of the Item based on the width of the screen (such as limiting the number of items to 3 per row).
How to describe these three scenarios in a concise and elegant way? The answer is NSCollectionLayoutDimension
class NSCollectionLayoutDimension {
class func fractionalWidth(_ fractionalWidth: CGFloat) - >Self
class func fractionalHeight(_ fractionalHeight: CGFloat) - >Self
class func absolute(_ absoluteDimension: CGFloat) - >Self
class func estimated(_ estimatedDimension: CGFloat) - >Self
}
Copy the code
NSCollectionLayoutDimension added according to the proportion of the parent view to describe the size of the fractionalWidth/fractionalHeight method, and the fixed value proportion, adaptive, unified packing up the three description way.
Let’s look at an example.
let size = NSCollectionLayoutDimension(widthDimension: .fractionalWidth(0.25),
heightDimension: .fractionalWidth(0.25))}Copy the code
Item
Group
Item
Group
After understanding this basis, let’s look at how NSCollectionLayoutDimension in Compositional Layout play a role.
-
NSCollectionLayoutSize
class NSCollectionLayoutSize { init(widthDimension: NSCollectionLayoutDimension,}Copy the code
Simple is used to describe the size of the Item, use NSCollectionLayoutDimension described above.
-
NSCollectionLayoutItem
class NSCollectionLayoutItem { convenience init(layoutSize: NSCollectionLayoutSize) var contentInsets: NSDirectionalEdgeInsets } Copy the code
Used to describe the complete layout information of an Item, including the above size, NSCollectionLayoutSize, and the margins, NSDirectionalEdgeInsets.
-
NSCollectionLayoutGroup
class NSCollectionLayoutGroup: NSCollectionLayoutItem { class func horizontal(layoutSize: NSCollectionLayoutSize.subitems: [NSCollectionLayoutItem]) -> Self class func vertical(layoutSize: NSCollectionLayoutSize.subitems: [NSCollectionLayoutItem]) -> Self class func custom(layoutSize: NSCollectionLayoutSize.itemProvider: NSCollectionLayoutGroupCustomItemProvider) - >Self } Copy the code
Describes the Group layout. It provides both vertical and horizontal directions. At the same time you can also achieve NSCollectionLayoutGroupCustomItemProvider custom Group layout.
It also receives a NSCollectionLayoutDimension, used to determine the size of the Group. Note that when an Item uses fractionalWidth/fractionalHeight, the size of the Group affects the size of the Item.
In addition, it has a subitems parameter of type NSCollectionLayoutItem array, which is used to pass items.
-
NSCollectionLayoutSection
class NSCollectionLayoutSection { convenience init(layoutGroup: NSCollectionLayoutGroup) var contentInsets: NSDirectionalEdgeInsets } Copy the code
Description of a Section layout. You can also change the Section margins by modifying the contentInsets.
These are the four classes used to describe Compositional Layout. With a precise description of the layout, we can get a very flexible UICollectionView layout without having to rewrite the complex UICollectionViewLayout. However, Compositional Layout’s playability doesn’t stop there, and some additional advanced Layout tricks are required for further customization.
Senior layout
NSCollectionLayoutAnchor
For Item, we may have a need for something similar to the iOS desktop dot. NSCollectionLayoutAnchor allows us to easily add custom widgets to an Item.
// NSCollectionLayoutAnchor
let badgeAnchor = NSCollectionLayoutAnchor(edges: [.top, .trailing],
fractionalOffset: CGPoint(x: 0.3, y: -0.3))
let badgeSize = NSCollectionLayoutSize(widthDimension: .absolute(20),
heightDimension: .absolute(20))
let badge = NSCollectionLayoutSupplementaryItem(layoutSize: badgeSize, elementKind: "badge", containerAnchor: badgeAnchor)
let item = NSCollectionLayoutItem(layoutSize: itemSize, supplementaryItems: [badge])
Copy the code
By having multiple classes describing the location, size, and view of the Anchor, we can easily add a custom Anchor to an Item.
NSCollectionLayoutBoundarySupplementaryItem
Headers and Footers are components that we use a lot. This comLayout weakens the concept of Headers and Footers. They are all NSCollectionLayoutBoundarySupplementaryItem, you can just by describing its position relative to the Section (top/bottom) to achieve the effect of the Header and Footer in the past.
// NSCollectionLayoutBoundarySupplementaryItem
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: "header", alignment: .top)
let footer = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: footerSize, elementKind: "footer", alignment: .bottom)
header.pinToVisibleBounds = true
section.boundarySupplementaryItems = [header, footer]
Copy the code
PinToVisibleBounds attribute is used to describe NSCollectionLayoutBoundarySupplementaryItem underline the screen at the CollectionView after the top, That’s the same Header style as before for Plain Style.
NSCollectionLayoutDecorationItem
Have you ever encountered such a UI requirement?
// Section Background Decoration Views
let background = NSCollectionLayoutDecorationItem.background(elementKind: "background")
section.decorationItems = [background]
// Register Our Decoration View with the Layout
layout.register(MyCoolDecorationView.self, forDecorationViewOfKind: "background")
Copy the code
Through NSCollectionLayoutDecorationItem, we can add a custom view to the background of the Section, the loading method and the Item Header Footer, need to register first.
Estimated Self-Sizing
With so many customization features added, Compositional Layout still supports adaptive sizing. This greatly facilitates the presentation of Dynamic content and provides better support for system features such as Dynamic text.
// Estimated Self-Sizing
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(44.0))
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize,
header.pinToVisibleBounds = true
elementKind: "header",
alignment: .top)
section.boundarySupplementaryItems = [header, footer]
Copy the code
Nested NSCollectionLayoutGroup
I don’t know if you noticed, but the subitems parameter in the initialization method of NSCollectionLayoutGroup is an array of NSCollectionLayoutItem, And NSCollectionLayoutGroup also inherits from NSCollectionLayoutItem, which means that NSCollectionLayoutGroup can be nested within NSCollectionLayoutGroup. The idea is that by nesting groups we can customize a more complex layout.
How is this Group described in code?
// Nested NSCollectionLayoutGroup
let leadingItem = NSCollectionLayoutItem(layoutSize: leadingItemSize) let trailingItem = NSCollectionLayoutItem(layoutSize: trailingItemSize)
let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: trailingGroupSize) subitem: trailingItem, count: 2)
let containerGroup = NSCollectionLayoutGroup.horizontal(layoutSize: containerGroupSize, subitems: [leadingItem, trailingGroup])
Copy the code
Consider how complex such a Layout would be if you implemented UICollectionViewLayout yourself, which can now be described very intuitively through the concise and abstract Compositional Layout API.
Orthogonal Scrolling Sections
This is the property that we mentioned earlier, that allows sections to scroll.
// Orthogonal Scrolling Sections
section.orthogonalScrollingBehavior = .continuous
Copy the code
By setting the Section orthogonalScrollingBehavior parameters, we can achieve a variety of different ways of rolling.
// Orthogonal Scrolling Sections
enum UICollectionLayoutSectionOrthogonalScrollingBehavior: Int {
case none
case continuous
case continuousGroupLeadingBoundary
case paging
case groupPaging
case groupPagingCentered
}
Copy the code
OrthogonalScrollingBehavior parameter is a UICollectionLayoutSectionOrthogonalScrollingBehavior type of enumeration, contains we used in the actual developers will almost all rolling way, Such as the common free-scrolling, scrolling by page, and scrolling by Group (including Group Leading and Group Center boundaries). Ever to achieve a similar effect, most of us need to implement UICollectionViewLayout or simply turn like AnimatedCollectionViewLayout third-party libraries, now that Apple has all for you!
open class func vertical(layoutSize: NSCollectionLayoutSize.subitem: NSCollectionLayoutItem.count: Int) - >Self
Copy the code
In contrast to the default API, subitem receives only a single Item instead of an array. But you’re smart enough to think that you can build a Group and pass it into the API) and add a count. This count causes the Group to try to cram in a number of items within its limited size. The end result is similar
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item, item, item])
Copy the code
However, the above code will not work because subitems are concerned with the combination of different items, not the number of actual items, so subitems will deduplicate the items in the array. So if you want to cram multiple items into a Group, the latter is your only option.
Does this give you a sense of what groups can do? In the example above, what would happen if we turned off Section scrolling?
There will still be three items in each Group, but due to Section width limitations, the next Group will have to be placed below the last Group, and the result will be a tableViewer-like layout. When we turn on scroll mode for Section, the magic happens. Since sections can scroll, there is a ContentView similar to a ScrollerView, and its child views can be rendered on a larger scale, so subsequent groups can follow to the right of previous groups, And finally fill the Section’s entire ContentView.
Now you can see why Apple introduced the Group concept. In fact, WHEN I was looking at the Advances in Collection View Layout, I was also bored. It was not until I finally saw the example of App Store that I realized that in order to realize multi-latitude scrolling (actually, the feature of Section scrolling is given), The original level is not enough to describe a complete multi-dimensional CollectionView, and an additional level is needed to describe the layer between the Section and Item. Now, if you think of the Section as the original CollectionView, you can think of the new Group as the original Section. Since the Section now acts as a CollectionView and is given the scrolling feature, an extra layer is needed to describe the “set of items” relationships described by the previous Section. The Group appears.
We can say that the existence of the Group is fully in service of this scrollable Section. Scrollable sections add a latitude flow to the CollectionView. If your CollectionView does not need multi-dimensional scrollable, Then you’ll find that the presence of groups in Compositional Layout is a completely unnecessary thing to do.
review
As I said earlier, the Compositional Layout hierarchy is Item > Group > Section > Layout.
conclusion
Compositional Layout gives us a more flexible and easy to use CollectionView Layout and multi-dimensional waterfall flow, which is a new upgrade for UICollectionView. It will give UICollectionView more possibilities. One point to note is that the New Compositional Layout is already available on the App Store on iOS 13, but the ability to rotate animations doesn’t work very well on the iPad, so the current version of ComLayout has yet to be optimized for controls. It’s going to be a while before we get to use it, but I can’t wait.
The official Demo shows almost all Compositional Layout layouts for iOS and macOS. I highly recommend that you follow the code and the results!
Using Collection View Compositional Layouts and Diffable Data Sources