At the WWDC 2019, Apple introduced a new form of UICollectionViewLayout.
If you ever worked with UICollectionView, you may have struggled with your layout attributes, or tweaking the FlowLayout. I, personally literally spent hours trying to get a good result, working for all iOS version, in every device, every size class, every orientation.
Well, meet UICollectionViewCompositionalLayout
!
A layout that allows you to specify your rows/items, groups and sections size and insets in a very simple way.
With Compositional Layouts, you could build very complex layout just within few lines of code.
Let's take a look at it.
To start using a CollectionViewLayout, let's make a quick collectionViewCell.
Here is a simple UICollectionViewCell that has a container with a shadow and a corner radius and a rounded colored view inside :
class Cell: UICollectionViewCell {
var container: UIView = {
let view = UIView()
view.backgroundColor = UIColor.white
view.layer.shadowColor = UIColor.black.cgColor
view.layer.cornerRadius = 4
view.layer.shadowOpacity = 0.5
view.layer.shadowRadius = 4
view.layer.shadowOffset = CGSize(width: 0, height: 2)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var colorView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.red
view.layer.cornerRadius = 10
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.contentView.addSubview(self.container)
self.container.topAnchor.constraint(equalTo: self.contentView.topAnchor).isActive = true
self.container.leftAnchor.constraint(equalTo: self.contentView.leftAnchor).isActive = true
self.container.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor).isActive = true
self.container.rightAnchor.constraint(equalTo: self.contentView.rightAnchor).isActive = true
self.container.addSubview(self.colorView)
self.colorView.centerXAnchor.constraint(equalTo: self.container.centerXAnchor).isActive = true
self.colorView.centerYAnchor.constraint(equalTo: self.container.centerYAnchor).isActive = true
self.colorView.widthAnchor.constraint(equalToConstant: 20).isActive = true
self.colorView.heightAnchor.constraint(equalToConstant: 20).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Looks like :
Now we will make a quick ViewController with a UICollectionView inside :
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var collectionView: UICollectionView!
let colors = [UIColor.red, UIColor.blue, UIColor.green, UIColor.orange, UIColor.purple]
override func viewDidLoad() {
super.viewDidLoad()
// init collection view
self.collectionView = UICollectionView()
self.collectionView.backgroundColor = UIColor.white
// set the delegate and dataSource on self
self.collectionView.delegate = self
self.collectionView.dataSource = self
// register our cell
self.collectionView.register(Cell.self, forCellWithReuseIdentifier: "cell")
// place the collectionView in the viewController's view
self.collectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.collectionView)
NSLayoutConstraint.activate([
self.collectionView.topAnchor.constraint(equalTo: self.view.layoutMarginsGuide.topAnchor),
self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
self.collectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
self.collectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor)
])
}
// data source
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? Cell {
cell.colorView.backgroundColor = colors[indexPath.row]
return cell
} else {
return UICollectionViewCell()
}
}
}
So now, we need to properly init the collectionView, with bounds and layout. Let's make a function, we'll call makeLayout()
that will return our compositional layout.
A UICollectionViewCompositionalLayout is initialized with a block that takes two parameters : the section, and the environment, and returns a NSCollectionLayoutSection. As you can guess, it will be called on render of every distinct section.
func makeLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { (section: Int, environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: NSCollectionLayoutDimension.fractionalWidth(1.0), heightDimension: NSCollectionLayoutDimension.absolute(44)))
item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(50))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
return section
}
return layout
}
In these few lines, we set a few notable objects :
- Item :
NSCollectionLayoutItem
initialized with its size (width & hieght). In this sample of code I also set contentInsets. This is basically a cell. - Group :
NSCollectionLayoutGroup
initilalized with its size, its item and count. A group represents a "line" of the layout. The count is the number of item in one line. Call it a column if you want. - Section :
NSCollectionLayoutSection
initialized with its group. It's, well, a section.
So we could make an interesting layout by playing the item count in groups like so :
Easy! We'll make a func numberOfColumns(section: Int) -> Int
and call it in the NSCollectionLayoutGroup
init.
func numberOfColumns(section: Int) -> Int {
switch section {
case 0:
return 5
case 1:
return 3
default:
return 1
}
}
Composition Layout allows also Orthogonal Scrolling, you know, like in the AppStore, with bi directional scrolling. But we'll keep that for another time.
Hope you enjoyed this little intro. If you want to go deeper in Compositional Layout, I highly recommend WWDC 2019 Session 215.
Happy Coding! :)
Top comments (0)