iOS 8中嵌套的UICollectionViews,AutoLayout和旋转

时间:2014-10-24 14:33:08

标签: ios rotation uicollectionview autolayout viewwilltransitiontosize

我开始将AutoLayout用于大型项目,并对此感到非常惊讶。但是,现在我必须调整项目以适应旋转和大小类,并且我很难让视图正常运行。

基本问题是我UICollectionViews单元格再次包含UICollectionViews 。旋转设备时,基本集合视图会正确调整其单元格,但嵌套集合视图的所有单元格都不会自动调整为新大小。

经过调查后,归结为:

  • 调用viewWillTransitionToSize时,基本集合视图尚未旋转(按预期方式)
  • 在调用viewDidLayoutSubviews之后的某个时候,基本集合视图现在具有旋转的大小 - 但遗憾的是它的单元格还没有 - 它们仍然具有旋转之前的宽度
  • 即使在coordinator animateAlongsideTransition ... completion:{}的{​​{1}}区块中,单元格也没有新的宽度。

上述意味着我没有可能告诉嵌套集合视图的单元格改变它们的大小,因为我还不知道新的宽度;并且autolayout系统不会自动调整它们。

其他人是否有同样的问题或知道解决方案?

7 个答案:

答案 0 :(得分:8)

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

主要部分在于将invalidateLayoutreloadData电话放在正确的位置:

  • invalidateLayout函数的基础collectionView的布局上放置willTransitionToSize
  • reloadData函数coordinator animateAlongsideTransition:调用的完成块中的基本collectionView上添加willTransitionToSize
  • 确保单元格中任何视图的自动布局约束设置正确 - 我将带有自动布局约束的单元格视图连接到代码中单元格的内容视图。
  • 如果您覆盖了集合视图布局的prepareLayout函数,请确保预先计算的单元格大小使用正确的宽度,我在这里使用了错误的宽度,这会导致一些其他问题。

答案 1 :(得分:4)

感谢TheEye。我想将此作为评论添加到他们的回复中,但显然我还没有足够的Karma(还),所以请考虑这是一个附录。

我遇到了同样的问题,但是我的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];       
}];  
}

答案 2 :(得分:1)

TheKey的回答是正确的。 但是,您可以直接调用

,而不是重新加载数据
UICollectionView.collectionViewLayout:finalizeCollectionViewUpdates()

示例:

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        self.collectionView.collectionViewLayout.invalidateLayout()
        self.collectionView.collectionViewLayout.finalizeCollectionViewUpdates()
    }

答案 3 :(得分: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()
}

答案 4 :(得分:0)

我也遇到了同样的问题,其他答案我发现布局正在变成letterboxed。我接近了我想要的东西:

 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()

        })

    }

答案 5 :(得分: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();
}

对于集合视图的数据,它可以通过使用现有的数据源工作得很好,这在从Web加载数据时非常有效。

答案 6 :(得分: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()
        }
    }
}