swiftuicollectionviewuicollectionviewcelluicollectionviewflowlayoutiglistkit

Multiple Sections Per Row using IGListKit or UICollectionView


I am trying to create a feed layout in this fashion

[-------2/3-------][---1/3---] /* 2 items - 2 thirds and 1 third */
[---1/3---][-------2/3-------] /* 2 items - 1 third and 2 thirds */
[-------2/3-------][---1/3---] /* 2 items - 2 thirds and 1 third */
[---1/3---][-------2/3-------] /* 2 items - 1 third and 2 thirds */

I was able to kind of achieve this by doing something like

class FeedSectionController: ListSectionController {

    var entry: FeedEntry!

    override init() {
        super.init()
        inset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
    }

    override func numberOfItems() -> Int {
        return 2
    }

    override func sizeForItem(at index: Int) -> CGSize {

        guard let ctx = collectionContext else { return .zero }

        if index == 0 {
            return CGSize(width: ((ctx.containerSize.width / 3) * 2), height: 30)
        } else {
            return CGSize(width: (ctx.containerSize.width / 3), height: 30)
        }
    }

    override func cellForItem(at index: Int) -> UICollectionViewCell {
        let cell = UICollectionViewCell()
        cell.backgroundColor = index % 2 == 0 ? .lightGray : .darkGray

        return cell
    }

    override func didUpdate(to object: Any) {
        entry = object as? FeedEntry
    }
}

However the result is really more like

[-------2/3-------][---1/3---] /* 2 items - 2 thirds and 1 third */
[-------2/3-------][---1/3---] /* 2 items - 2 thirds and 1 third */
[-------2/3-------][---1/3---] /* 2 items - 2 thirds and 1 third */
[-------2/3-------][---1/3---] /* 2 items - 2 thirds and 1 third */

Which is not quite right, also it means I have 2 entire feed items per cell, per section.

What I'd like is each section to contain X amount of cells that essentially construct a feed item.

To do this however I need to to layout FeedSectionController in the fashion outlined earlier, not the items within FeedSectionController (I think).


Solution

  • I'm not 100% familiar with IGListKit but you could create that effect with just a standard UICollectionView

    
    class FeedViewController: UIViewController {
    
        private enum FeedItemWidth {
            case oneThird, twoThirds
        }
    
        private lazy var collectionView: UICollectionView = {
            let layout = UICollectionViewFlowLayout()
            let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
            cv.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "UICollectionViewCell")
            cv.backgroundColor = .gray
            cv.translatesAutoresizingMaskIntoConstraints = false
            return cv
        }()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            collectionView.delegate = self
            collectionView.dataSource = self
    
            if let collectionViewLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
                collectionViewLayout.minimumInteritemSpacing = 0
                collectionViewLayout.sectionInset = .init(top: 8, left: 8, bottom: 8, right: 8)
            }
    
            view.backgroundColor = .lightGray
    
            view.addSubview(collectionView)
    
            NSLayoutConstraint.activate([
                collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
                collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
                collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
                collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
            ])
        }
    
        override func viewWillLayoutSubviews() {
            super.viewWillLayoutSubviews()
            collectionView.collectionViewLayout.invalidateLayout()
        }
    
        private func feedItemWidth(_ type: FeedItemWidth) -> CGFloat {
            switch type {
            case .oneThird:
                return (collectionView.bounds.width / 3) - 12
            case .twoThirds:
                return  ((collectionView.bounds.width / 3) * 2) - 12
            }
        }
    }
    
    extension FeedViewController: UICollectionViewDelegateFlowLayout {
        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    
            let layoutOptions = [
                CGSize(width: feedItemWidth(.oneThird), height: 100),
                CGSize(width: feedItemWidth(.twoThirds), height: 100),
                CGSize(width: feedItemWidth(.twoThirds), height: 100),
                CGSize(width: feedItemWidth(.oneThird), height: 100)
            ]
    
            return layoutOptions[indexPath.item % layoutOptions.count]
        }
    
        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
            return 8
        }
    
    }
    
    extension FeedViewController: UICollectionViewDataSource {
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return 35
        }
    
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "UICollectionViewCell", for: indexPath)
            cell.backgroundColor = (indexPath.item % 2 == 0) ? .white : .black
            return cell
        }
    }