3

I started to use AutoLayout for a large project and was positively surprised about it. However, now I have to adjust the project to accommodate for rotation and size classes, and I have big troubles getting the views to behave correctly.

The base problem is that I have UICollectionViews with cells that again contain UICollectionViews. When the device is rotated, the base collection view adapts it's cells right, but all cells of the nested collection views are not adjusted to the new size automatically.

After investigation it comes down to:

  • When viewWillTransitionToSize is called, the base collection view is not rotated yet (as expected)
  • Sometime after that viewDidLayoutSubviews is called, the base collection view now has the rotated size - but unfortunately its cells not yet - they still have the width as BEFORE the rotation
  • Even in the coordinator animateAlongsideTransition ... completion:{} block in viewWillTransitionToSize the cells do not have their new width yet.

The above means that there is no place where I could possible tell the cells of the nested collection views to change their size, as I don't know the new widths yet; and the autolayout system does not automatically adjust them.

Does anyone else have the same problem or know a solution?

4

7 回答 7

9

在这里回答我自己的问题 - 我终于找到了解决方案。对于任何其他遇到此问题的人,这是我所做的:

主要部分在于在正确的位置放置invalidateLayoutreloadData调用:

  • 在函数中放置一个invalidateLayout基础 collectionView 的布局willTransitionToSize
  • 在函数调用的完成块中放置一个reloadData基础collectionView 。coordinator animateAlongsideTransition:willTransitionToSize
  • 确保单元格中任何视图的自动布局约束设置正确 - 我将具有自动布局约束的单元格视图连接到代码中单元格的内容视图。
  • 如果您覆盖了prepareLayout集合视图布局的功能,请确保预先计算的单元格大小使用正确的宽度,我在这里使用了错误的宽度,这会导致一些额外的问题。
于 2014-10-28T08:40:44.470 回答
5

感谢 TheEye。我想将此作为评论添加到他们的回复中,但显然我还没有足够的业力(还),因此请将此视为附录。

我遇到了同样的问题,但是我的 uicollectionview 中的单元格在选择时具有“扩展状态”。如果用户旋转设备并且我们调用[self.collectionview reloadData],任何展开的单元格都将失去其状态。您可以选择调用performBatchUpdates并强制集合视图进行布局,而不会丢失任何单元格的状态

-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator{
[self.collectionView.collectionViewLayout invalidateLayout];

[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {

} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
    //[self.collectionView reloadData];//this works but reloading the view collapses any expanded cells.       
    [self.collectionView performBatchUpdates:nil completion:nil];       
}];  
}
于 2015-03-10T14:59:03.733 回答
1

想提供一个替代解决方案。我不喜欢在协调器的动画完成中调用 reloadData 或 performBatchUpdates 的轻微延迟。两者都导致了 AutoLayout 警告。

在 viewWillTransitionToSize 中,在所有可见的嵌套集合视图上调用 invalidateLayout,最后在外层/顶层集合视图上调用 invalidateLayout。

我的集合视图单元格有一个子集合视图:

class NestableCollectionViewCell: UICollectionViewCell {
  @IBOutlet weak var collectionView: NestableCollectionView?
}

扩展 UICollectionView 以使嵌套的集合视图无效:

class NestableCollectionView: UICollectionView {

  func invalidateNestedCollectionViews(){
    let visibleCellIndexPaths = self.indexPathsForVisibleItems()
    for cellIndex in visibleCellIndexPaths {
      if let nestedCell = self.cellForItemAtIndexPath(cellIndex) as? NestableCollectionViewCell {
        // invalidate any child collection views
        nestedCell.collectionView?.invalidateNestedCollectionViews()
        // finally, invalidate the cell's own collection view
        nestedCell.collectionView?.collectionViewLayout.invalidateLayout()
      }
    }
  }
}

并且,在 willTransitionToSize 上无效:

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
  super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

  self.collectionView.invalidateNestedCollectionViews()
  self.collectionView.collectionViewLayout.invalidateLayout()
}
于 2015-12-23T17:26:33.593 回答
1

TheKey的反应是正确的。但是,您可以调用而不是重新加载数据

UICollectionView.collectionViewLayout:finalizeCollectionViewUpdates()

例子:

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        self.collectionView.collectionViewLayout.invalidateLayout()
        self.collectionView.collectionViewLayout.finalizeCollectionViewUpdates()
    }
于 2015-08-27T11:26:45.003 回答
0

我也遇到了同样的问题,我发现其他答案的布局被信箱化了。我更接近我想要的:

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

        super.viewWillTransition(to: size, with: coordinator)
        coordinator.animate(alongsideTransition: nil, completion:  { context in

            self.collectionView?.collectionViewLayout.invalidateLayout()
            self.collectionView?.collectionViewLayout.finalizeCollectionViewUpdates()

        })

    }
于 2018-06-10T05:26:44.687 回答
0

这是我们应用程序中一直存在的问题,但最终我实现了我认为是迄今为止最好和最简单的解决方案。基本上,当设备旋转时,您需要从超级视图中删除父(基础)集合视图,然后将其再次添加到超级视图中。如果你做得好,系统甚至会为你制作动画!这是一个例子:

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

    self.collectionView.removeFromSuperview();
    self.collectionView = self.getAFreshCollectionViewInstance();
    self.setupCollectionViewFrameUsingAutoLayout();
}

至于collection view的数据,使用现有的数据源就可以了,从web加载数据时效率很高。

于 2018-06-17T04:50:48.600 回答
0

以防万一有人在寻找Swift 4解决方案:

我有一个非常相似的问题,我发现解决方案是上面两个答案的组合:使用协调器动画函数在顶部集合视图上调用 invalidateLayout(),然后在完成处理程序中,循环遍历每个单元格并调用 invalidateLayout () 在每个单元格的集合视图上。我不必维护选择或扩展视图,因此无需调用 reloadData() 即可工作。

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

    // get the device orientation so we can change the number of rows & columns
    // in landscape and portrait modes
    let orientation = UIDevice.current.orientation

    if orientation.isLandscape {
        self.columns = 4
        self.rows = 3
    } else {
        self.columns = 3
        self.rows = 4
    }

    // invalidate the top collection view inside the coordinator animate function
    // then inside the animation completion handler, we invalidate each cell's colleciton view
    coordinator.animate(alongsideTransition: { (context) in

        self.collectionView.collectionViewLayout.invalidateLayout()

    }) { (context) in

        let cells = self.collectionView.visibleCells

        for cell in cells {
            guard let cell = cell as? MyCollectionViewCell else { continue }
            cell.invalidateLayout()
        }
    }
}
于 2018-08-16T18:57:49.907 回答