visit
import UIKit
class QuickCell: UICollectionViewCell {
let containerView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
configureViews()
}
func configureViews() {
addSubview(containerView)
containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
containerView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In our ViewController, we will set up the collection view:
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var collectionView: UICollectionView!
let dataColors = [
[UIColor.red, UIColor.blue, UIColor.green, UIColor.magenta, UIColor.purple, UIColor.orange, UIColor.black, UIColor.lightGray, UIColor.blue],
[UIColor.red, UIColor.blue, UIColor.green, UIColor.magenta, UIColor.blue]
]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
collectionView = UICollectionView(frame: .zero, collectionViewLayout: createCustomLayout())
collectionView.backgroundColor = .white
self.collectionView.delegate = self
self.collectionView.dataSource = self
self.collectionView.register(QuickCell.self, forCellWithReuseIdentifier: "cellID")
configureCollectionView()
}
func configureCollectionView() {
collectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(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)
])
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return dataColors.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataColors[section].count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellID", for: indexPath) as? QuickCell {
let colorArray = dataColors[indexPath.section]
cell.containerView.backgroundColor = colorArray[indexPath.row]
return cell
} else {
return UICollectionViewCell()
}
}
The above code is quite simple. I have created an array which has two arrays in it. Then I have created a collectionView and initiated it with a createCustomLayout() function (more on this in a bit). Then I have just added the collectionView as a subView and configured auto-layout constraints.
Now let's get to the fun stuff!The main thing to understand here is what is Item, Group and Section. We design a section by creating a group. That group then contains items.
func createCustomLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { (section: Int, environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let leadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: NSCollectionLayoutDimension.fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)))
leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
let leadingGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7), heightDimension: .fractionalHeight(1))
let leadingGroup = NSCollectionLayoutGroup.vertical(layoutSize: leadingGroupSize, subitem: leadingItem, count: 1)
let trailingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: NSCollectionLayoutDimension.fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)))
trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
let trailingGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3), heightDimension: .fractionalHeight(1))
let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: trailingGroupSize, subitem: trailingItem, count: 2)
let containerGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(250))
let containerGroup = NSCollectionLayoutGroup.horizontal(layoutSize: containerGroupSize, subitems: [leadingGroup, trailingGroup])
let section = NSCollectionLayoutSection(group: containerGroup)
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 0, bottom: 20, trailing: 0)
return section
}
return layout
}
}
let leadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: NSCollectionLayoutDimension.fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)))
fractionalWidth and fractionalHeight represent how wide and high should the item be as compared to its parent container. For items, their parent container is group. Then we will create the leadingGroup. For that we need to create the group size.
let leadingGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7), heightDimension: .fractionalHeight(1))
let leadingGroup = NSCollectionLayoutGroup.vertical(layoutSize: leadingGroupSize, subitem: leadingItem, count: 1)
let trailingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: NSCollectionLayoutDimension.fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)))
trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
let trailingGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3), heightDimension: .fractionalHeight(1))
let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: trailingGroupSize, subitem: trailingItem, count: 2)
Similar to the leadingItem, this item also takes 100% dimensions of the parent container. However, the group size is 30% of the width and height is 100% of the section height. The trailingGroup however is initialized by giving the count parameter a value of 2. This overrides the item's height dimensions and divides the height by 2.
So now the leading group contains one item and the trailing group contains two items. We can have different number of items stacked vertically in either group just by changing the count parameter.Now let's create the containerGroup, that will hold the entire sub groups. This is called nested-groups. You can have unlimited nested groups inside your layout. So essentially we have a group, that contains two different groups, each group then contains items.
This is the containerGroup:
let containerGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(250))
let containerGroup = NSCollectionLayoutGroup.horizontal(layoutSize: containerGroupSize, subitems: [leadingGroup, trailingGroup])
The containerGroup has absolute height of 250 and the width of the collectionView. Then we are using the horizontal axis for the layout group and giving it the group size and an array of subitems, which are leadingGroup and trailingGroup.
Finally we create the section, and provide the section an orthogonalScrollingBehavior to get horizontal scrolling within each section. We get six different behaviors to choose from - I have selected continuousGroupLeadingBoundary, which always align the group's leading boundary whenever you scroll.
Here is the app in action: