嵌套的UICollectionView,在iOS 8中的自动布局和旋转

3
我开始在一个大项目中使用AutoLayout,并对其感到非常满意。但现在我必须调整项目以适应旋转和大小类,我发现让视图正确运作很困难。根本问题是我有包含UICollectionView的单元格的UICollectionViews。当设备旋转时,基本集合视图会自适应其单元格,但嵌套集合视图的所有单元格不会自动调整到新的大小。
经过调查,我发现:
- 当调用viewWillTransitionToSize时,基本集合视图尚未旋转(按预期)。 - 在此之后,将调用viewDidLayoutSubviews,基本集合视图现在具有旋转大小,但遗憾的是它的单元格还没有——它们仍然具有旋转之前的宽度。 - 即使在viewWillTransitionToSize中的协调器animateAlongsideTransition...completion:{}块中,单元格仍然没有新的宽度。
上述情况意味着我无法告诉嵌套集合视图的单元格更改其大小,因为我还不知道宽度的新值;而且自动布局系统不会自动调整它们。
是否有其他人遇到了相同的问题或者知道解决方案?
7个回答

9

以下是我自己的问题答案 - 我终于找到了解决方法。对于其他遇到困难的人,这是我所做的:

关键在于在正确的位置上调用invalidateLayoutreloadData

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

我已经寻找这个答案一年多了。在模态视图控制器上进行旋转一直失败,现在我明白了缺少什么。我在这里实现了一个示例:https://bitbucket.org/hansdesmedt/pattern-uicollectionviewlayout-ios - Hans

5
感谢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];       
}];  
}

1
我希望能提供一种替代方案。我不喜欢在协调器动画完成时调用reloadData或performBatchUpdates时出现的轻微延迟。这两者都会导致自动布局警告。
在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()
}

1

TheKey的回答是正确的。 不过,您可以直接调用而不是重新加载数据。

UICollectionView.collectionViewLayout:finalizeCollectionViewUpdates()

例子:

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

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

        })

    }

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

至于集合视图的数据,通过使用现有的数据源是完全正常的,当数据从网络加载时非常高效。


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接