UICollectionViewFlowLayout properties settings do not work when using auto layout

I am trying to customize a UICollectionView using a UICollectionViewFlowLayout with the following requirement: the minimum width must always be exactly one third of the height of the UICollectionView. My initial thought was to override viewDidLayoutSubviews like this:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    collectionViewFlowLayout.minimumLineSpacing = collectionView.frame.height / 3
    collectionViewFlowLayout.invalidateLayout()
}

      

Note that I am using viewDidLayoutSubviews because I plan on using Auto Layout and the frame may be subject to some tricky constraints. So I cannot calculate the frame myself, but I need to wait until Auto Layout calculates it for use in viewDidLayoutSubviews.

I tested this a bit by programming the UICollectionView (and rotating the simulator to make sure the minimum linear snapping is always correct). Everything seemed to be working fine.

Then I switched to automatic layout. I just constrained the view of the collection top, bottom, leading and trailing space to my supervisor. After that setting the minimum width didn't have the desired effect, it didn't change anything in the collection view.

The following code demonstrates this problem well. Once I set useAutoLayout

to true

, setting the minimum width won't work anymore.

class DemoViewController: UIViewController, UICollectionViewDataSource {

    var collectionView: UICollectionView!

    var collectionViewFlowLayout: UICollectionViewFlowLayout!

    // MARK: - UIViewController

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionViewFlowLayout = UICollectionViewFlowLayout()
        collectionViewFlowLayout.itemSize = CGSizeMake(100, 100)

        collectionView = UICollectionView(frame: view.frame, collectionViewLayout: collectionViewFlowLayout)
        collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
        collectionView.dataSource = self

        view.addSubview(collectionView)

        let useAutoLayout = false // Change this to true to test!

        if useAutoLayout {
            collectionView.setTranslatesAutoresizingMaskIntoConstraints(false)

            NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[collectionView]-|", options: nil, metrics: nil, views: ["collectionView" : collectionView]))
            NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[collectionView]-|", options: nil, metrics: nil, views: ["collectionView" : collectionView]))
        } else {
            collectionView.autoresizingMask = .FlexibleHeight | .FlexibleWidth
        }
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        collectionViewFlowLayout.minimumLineSpacing = collectionView.frame.height / 3
        collectionViewFlowLayout.invalidateLayout()
    }

    // MARK: - <UICollectionViewDataSource>

    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return 1
    }

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

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! UICollectionViewCell

        cell.backgroundColor = UIColor.greenColor()

        return cell
    }

}

      

Test this code in the Simulator, rotate it and see how setting the minimum width does nothing when useAutoLayout

set to true

. So my question is, how can I use auto-layout and still provide a minimal interface?

Notes
The base SDK is installed in the iOS 8.4 SDK. Setting other properties such as itemSize

or minimumInteritemSpacing

does not work either.

+3


source to share


1 answer


I have reproduced what you described. It is very strange.

I think there is something about rotation that is causing the ignore in this context invalidLayout()

. Perhaps the problem is that your call invalidateLayout()

occurs at the moment when the layout object thinks it has already responded, or is in the process of responding, to a layout invalidation automatically generated by changing the rotation and subsequent collection view constraint.Then your invalidation is ignored because that it is too late to merge into automatic, and too early to be considered a separate invalidity. I guess. I notice that you can even increase the increment minimumLineSpacing

there, and it will happily progress ad infinitum if browsing the collection never has the wits to do the layout again.

But if you set up the view controller so that the shake event is invalidated, it notices the value.

So you can fix this problem by forcing invalidation to happen on the next rotation of the startup loop, thus avoiding any strange special logic blocking it during rotation. For example, if you replace viewDidLayoutSubviews()

like this:



override func viewDidLayoutSubviews() {
  super.viewDidLayoutSubviews()

  let newValue = collectionView.bounds.height / 3
  dispatch_async(dispatch_get_main_queue()) {
    [weak collectionViewFlowLayout] in
    collectionViewFlowLayout?.minimumLineSpacing = newValue
  }

      

then it works.

Why is this needed? I dont know. I don't think this is necessary. It seems like a mistake to UICollectionView

me, or at least very unintuitive part of API.

+1


source







All Articles