DEV Community

Maarek
Maarek

Posted on • Edited on

Replicating the AppStore's CollectionViewLayout (orthogonal) in Swift 5

Building a simple bi-directional scrolling UICollectionView

In a precedent post I scratched the surface of the freshly released CollectionView Compositional Layout API to get simpler and more complex CollectionViewLayout.
The today's challenge is to replicate this orthogonal scrolling CollectionView layout that you might have seen in your iDevice's AppStore ("Apps" or "Games" tab).

Basically we have some featured content in wide cards, with horizontal scroll, the app list that scrolls vertically, just like a regular TableView, and some basic blocks of content, maybe headers or footers with text.

Alt Text

So let's start by building our cells :

  • the "featured cell" card, having the "card" look, rounded corner, shadow and stuff with a large title and a smaller label ;
  • an "app cell", that contains an app icon, title and some category ;
  • and a "text cell" : just a basic cell with a multi-line label.

I am not going to detail this part as it's not the point of this post but you can check the code, at the end of the article.

Alt Text

Right now the ViewController is empty, I'm going to add a simple function that will return a UICollectionViewLayout that we will use in the CollectionView initializer.

    func makeLayout() -> UICollectionViewLayout {
        let layout = UICollectionViewCompositionalLayout { (section: Int, environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
            //
        }
        return layout
    }
Enter fullscreen mode Exit fullscreen mode

Now you know, the UICollectionViewCompositionalLayout block should return a NSCollectionLayoutSection. And here we have 3 section : the one with the features card, the one with the app list and the one with our text cell.

The horizontal scrolling sections' Layout

        let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)))
        item.contentInsets = NSDirectionalEdgeInsets = NSDirectionalEdgeInsets(top: 0.0, leading: 12.0, bottom: 0.0, trailing: 12.0)
        let group = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .fractionalHeight(0.25)), subitem: item, count: 1)
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 16.0, leading: 0.0, bottom: 16.0, trailing: 0.0)
        section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
        return section
Enter fullscreen mode Exit fullscreen mode

Let's detail every line of this sample of code, but first, try to keep in mind what is an item, a group and a section, as seen on my previous post about compositional layout :

  • I first declare our NSCollectionLayoutItem, with it's size, saying it's going to take 100% of the parent's width and height.
  • I give this item the insets I want.
  • Then, I initialize a group, a NSCollectionLayoutGroup, with the size I want each cell to take horizontally. In this case : 90% of the total collectionView width, 25% of the collectionView height. (I want 90% so users can see there is more on the left so they want to scroll πŸ˜‡)
  • I finally create my section with my group.
  • Add some insets.
  • This continuousGroupLeadingBoundary allows the vertical scrolling, but we can put .paging or any OrthogonalScrollingBehavior.

That's all it takes for the Featured Card's section. We'll put all that in an isolated piece of function that we'll call something like buildHorizontalSectionLayout() and simply return it in our block like so :

    func makeLayout() -> UICollectionViewLayout {
        let layout = UICollectionViewCompositionalLayout { (section: Int, environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
            if section == 0 {
                return self.buildHorizontalSectionLayout()
            }
        }
        return layout
    }
Enter fullscreen mode Exit fullscreen mode

The vertical scrolling sections' Layout

Now for the vertical scrolling, we'll simply mimic the tableView behaviour with self sizing cells. We want full with rows with an estimated height, adjustable according it's content's constraints.

        let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)))
        item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),  heightDimension: .estimated(70))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 12, bottom: 0, trailing: 12)
Enter fullscreen mode Exit fullscreen mode
  • An Item that takes 100% of width/height of it's superview.
  • A group that will have an estimated height, say 70, and 1 item per group.
  • Finally the section with the group and I specified insets.

I will also encapsulate this piece of code in a buildVerticalSectionLayout func, so now we now can update our makeLayout func :

    func makeLayout() -> UICollectionViewLayout {
        let layout = UICollectionViewCompositionalLayout { (section: Int, environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
            if section == 0 {
                return self.buildHorizontalSectionLayout()
            } else {
                return self.buildVerticalSectionLayout()
            }
        }
        return layout
    }
Enter fullscreen mode Exit fullscreen mode

The viewController will be pretty basic, and I am not going to detail the code. Basically just add a CollectionView, init with a frame and a layout (calling the makeLayout func), place it with constraints, register your cells, and set the delegate and dataSource.

    lazy var collectionView: UICollectionView = {
        let collectionView: UICollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: self.makeLayout())
        collectionView.backgroundColor = UIColor.white
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.register(Cell.self, forCellWithReuseIdentifier: "cell")
        collectionView.register(Featured.self, forCellWithReuseIdentifier: "featured")
        collectionView.register(Text.self, forCellWithReuseIdentifier: "text")
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        return collectionView
    }()
Enter fullscreen mode Exit fullscreen mode

And there you go !
Here is the final result :

Alt Text

Bonus : you can easily build an horizontal scrolling vertical list like so
Alt Text
by mixing the two layout examples shown above. Just use a vertical NSCollectionLayoutGroup and set an orthogonalScrollingBehavior (in the gif, it's .groupPaging). Don't forget to specify how many items you want in your group (how many "app cell" in a paged block) in the parameter count of you NSCollectionLayoutGroup.

Bonus 2 : You can use compositional Layouts with iOS 12 using IBPCollectionViewCompositionalLayout πŸ˜‹

Hope you enjoyed this quick walkthrough of UICollectionViewCompositionalLayout. Of course you can download the code.
Play with it, tweak it and make something great! ☺️

Happy coding!

Top comments (0)