1

I am attempting to resize the collectionView cell inside the masterViewController of a SplitViewController but the resizing did not work. I have implemented the necessary viewWillTransition and invalidateLayout and it turns out fine if it isn't housed inside a splitViewController. In my case, I have full screen cell size.

Here's my implementation:

//This CollectionController is housed inside a TabBarController. 
//The TabBarController is the masterViewController
class CollectionController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

     lazy var collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        let cv = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
        cv.translatesAutoresizingMaskIntoConstraints = false
        cv.backgroundColor = .gray
        cv.dataSource = self
        cv.delegate = self
        layout.scrollDirection = .horizontal
        cv.isPagingEnabled = true
        cv.showsHorizontalScrollIndicator = false
        cv.showsVerticalScrollIndicator = false
        cv.contentInsetAdjustmentBehavior = .always
        cv.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
        return cv
    }()

    var spacing: CGFloat = 16

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(collectionView)
        collectionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true

    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 3
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        cell.backgroundColor = .blue
        return cell
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 16
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 16
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let layoutFrame = view.safeAreaLayoutGuide.layoutFrame
        let size = CGSize(width: layoutFrame.width, height: layoutFrame.height)
        print("SizeForItem", size)

        return setUpCellSize(size: size)
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)

        if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            print("Transit1", size)

            layout.itemSize = setUpCellSize(size: size) //With or without this makes no difference
            layout.invalidateLayout()

            print("Transit2", size)

        }
    }

    func setUpCellSize(size: CGSize) -> CGSize {
        return size
    }
}

This is the result if implemented purely in the TabBarController, ie not inside a splitViewController. Noticed that cells (in blue) are positioned well beneath the status bar. Result is as what is expected and desired.

enter image description here

This is the result if implemented inside a splitViewController. Noticed that on first rotation to landscape, the cells went above the status bar. And when rotated back to portrait, the cell did not resize back to full screen.

enter image description here

Is there something else that needs to be implemented if the collectionView is placed inside a masterViewController?

EDIT

I add print statements inside sizeForItem and viewWillTransition. It turns out that on second rotation, the new size in viewWillTransition were not passed to the sizeForItem.

Even explicit calls to setupCellSize didn't update correctly. It also give the The behavior of the UICollectionViewFlowLayout is not defined warning. See gif (Gif starts when iPad is in landscape mode).

Also notice that when iPad rotates from landscape -> portrait, blue cell anchors to bottom of statusBar and top of tabBar. This is desired. Then when rotated back from portrait -> landscape, blue cell exceeds statusBar and went underneath tabBar. This is not what I want.

enter image description here

Koh
  • 2,687
  • 1
  • 22
  • 62

2 Answers2

1

You need to embed the CollectionController (the MASTER CONTROLLER) in a UINavigationController. If you really don't want a navigationBar, just hide it.

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    self.navigationController?.navigationBar.isHidden = true
}

enter image description here

Glenn Posadas
  • 12,555
  • 6
  • 54
  • 95
  • Hi, the peculiar issue is that when it transits, the `size` isn't updated to the newly rotated size until after it has been rotated the second time. How do I force it to update on the first rotation? – Koh Apr 26 '20 at 09:17
  • I noticed that your solution still pushes the cell upwards. Notice that in portrait the cell is anchored nicely to the bottom of the navBar. On rotate, the cell went upwards (can see the blue underneath the navBar). – Koh Apr 27 '20 at 03:55
  • your answer helped me find the solution, as such I have given this answer an upvote. – Koh Apr 28 '20 at 04:10
  • Sorry, I was busy, I just got hired in a new job, and learning some stuff. Good work. – Glenn Posadas Apr 28 '20 at 05:56
0

Ok, I have managed to solve this issue. @Glenn's solution helped partially. The portion that helped resolved this entirely is from this post.

First, implement @Glenn's solution on embedding CollectionController in a UINavigationController and hiding the navBar (since I do not want the navBar).

Second, add invalidateLayout in viewWillLayoutSubviews.

Full Code:

class CollectionController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

     lazy var collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal

        let cv = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
        cv.translatesAutoresizingMaskIntoConstraints = false
        cv.backgroundColor = .gray
        cv.dataSource = self
        cv.delegate = self
        cv.isPagingEnabled = true
        cv.alwaysBounceHorizontal = true
        cv.showsHorizontalScrollIndicator = false
        cv.showsVerticalScrollIndicator = false
        cv.contentInsetAdjustmentBehavior = .always
        cv.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
        return cv
    }()

    var spacing: CGFloat = 16

    //FIRST
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.navigationBar.isHidden = true
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(collectionView)
        collectionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 1
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        cell.backgroundColor = .blue
        return cell
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let layoutFrame = view.safeAreaLayoutGuide.layoutFrame
        let size = CGSize(width: layoutFrame.width, height: layoutFrame.height)
        return setUpCellSize(size: size)
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        collectionView.collectionViewLayout.invalidateLayout()
    }

    //SECOND
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()            
        collectionView.collectionViewLayout.invalidateLayout()
    }


    func setUpCellSize(size: CGSize) -> CGSize {
        return size
    }
}

Result like so, blue cell anchored nicely to the bottom of statusBar and top of tabBar. enter image description here

A side note, I attempted some of the solution in this thread and these didn't work for me:

  • returning shouldInvalidateLayoutForBoundsChange to true as suggested here
  • subclassing UICollectionViewFlowLayout as suggested here

If anyone out there decides to attempt the solution for the same case in a splitViewController can take note.

Koh
  • 2,687
  • 1
  • 22
  • 62