使用自动布局时设置 UICollectionViewFlowLayout 的属性不起作用

Settings UICollectionViewFlowLayout's properties not working when using Auto Layout

我正在尝试使用 UICollectionViewFlowLayout 设置 UICollectionView,并满足以下要求:minimumLineSpacing 应该始终恰好是 UICollectionView 高度的三分之一。我最初的想法是像这样覆盖 viewDidLayoutSubviews

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

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

请注意,我使用 viewDidLayoutSubviews 因为我打算使用自动布局,并且框架可能取决于一些复杂的约束。所以我不能自己计算框架,但必须等到自动布局为我计算它才能在 viewDidLayoutSubviews.

中使用

我通过以编程方式创建 UICollectionView(并旋转模拟器以查看 minimumLineSpacing 是否始终正确)对此进行了一些测试。它似乎工作得很好。

然后,我切换到自动布局。我只是将集合视图的顶部、底部、前导和尾随 space 约束到它的父视图。这样做之后,设置 minimumLineSpacing 不再有预期的效果,它根本没有改变集合视图的外观。

下面的代码很好地演示了这个问题。一旦我将 useAutoLayout 设置为 true,设置 minimumLineSpacing 就不再起作用了。

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
    }

}

在模拟器中测试此代码,旋转它并查看当 useAutoLayout 设置为 true 时设置 minimumLineSpacing 如何不起作用。所以我的问题是:如何使用自动布局并仍然提供 minimumLineSpacing?

备注
基础 SDK 设置为 iOS 8.4 SDK。设置 itemSizeminimumInteritemSpacing 等其他属性也不起作用。

我已经复制了你描述的内容。很奇怪。

我认为在这种情况下,旋转会导致 invalidLayout() 被忽略。也许问题在于您对 invalidateLayout() 的调用发生在布局对象认为它已经响应或正在响应由旋转和随之而来的边界更改自动产生的布局失效的位置集合视图。然后您的失效将被忽略,因为它来不及合并到自动失效中,而且太早无法算作单独的失效。我正在猜测。我注意到你甚至可以在那里继续递增 minimumLineSpacing ,它会愉快地前进到无穷大,而集合视图永远不会有智慧再次进行布局。

但是如果您设置视图控制器以便摇动事件触发失效,那么它会注意到该值。

所以你可以通过强制在 运行 循环的下一轮发生失效来解决这个问题,从而避免在轮换期间阻止它的任何奇怪的特殊逻辑。例如,如果您将 viewDidLayoutSubviews() 替换为以下内容:

override func viewDidLayoutSubviews() {
  super.viewDidLayoutSubviews()

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

然后就可以了。

为什么这是必要的?我不知道。我认为没有必要。对我来说,这感觉像是 UICollectionView 中的一个错误,或者至少是 API 中一个非常不直观的片段。