【问题标题】:Nested UICollectionViews, AutoLayout and rotation in iOS 8iOS 8 中的嵌套 CollectionView、自动布局和旋转
【发布时间】:2014-10-24 14:33:08
【问题描述】:

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

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

经过调查,归结为:

  • 调用viewWillTransitionToSize 时,基础集合视图尚未旋转(如预期的那样)
  • 在调用 viewDidLayoutSubviews 之后的某个时间,基本集合视图现在具有旋转后的大小 - 但不幸的是它的单元格还没有 - 它们仍然具有旋转前的宽度
  • 即使在viewWillTransitionToSizecoordinator animateAlongsideTransition ... completion:{} 块中,单元格也没有新的宽度。

以上意味着我无法告诉嵌套集合视图的单元格更改它们的大小,因为我还不知道新的宽度;并且自动布局系统不会自动调整它们。

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

【问题讨论】:

    标签: ios rotation uicollectionview autolayout viewwilltransitiontosize


    【解决方案1】:

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

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

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

    【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      TheKey 的反应是正确的。 但是,您可以调用

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

      例子:

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

      【讨论】:

        【解决方案4】:

        想要提供替代解决方案。我不喜欢在协调器的动画完成中调用 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()
        }
        

        【讨论】:

          【解决方案5】:

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

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

          【讨论】:

            【解决方案6】:

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

            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加载数据时效率很高。

            【讨论】:

              【解决方案7】:

              以防万一有人在寻找 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()
                      }
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 2015-06-05
                • 2014-04-27
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-10-02
                • 2012-10-14
                • 1970-01-01
                相关资源
                最近更新 更多