Swift:如何在设备旋转后刷新UICollectionView布局

Tal*_*han 52 flowlayout ios uicollectionview swift

我使用UICollectionView(flowlayout)来构建一个简单的布局.每个单元格的宽度设置为使用的屏幕宽度self.view.frame.width

但是当我旋转设备时,单元格不会更新.

在此输入图像描述

我找到了一个函数,在调整方向时调用它:

override func willRotateToInterfaceOrientation(toInterfaceOrientation: 
  UIInterfaceOrientation, duration: NSTimeInterval) {
    //code
}
Run Code Online (Sandbox Code Playgroud)

但我无法找到更新UICollectionView布局的方法

主要代码在这里:

class ViewController: UIViewController , UICollectionViewDelegate , UICollectionViewDataSource , UICollectionViewDelegateFlowLayout{

    @IBOutlet weak var myCollection: UICollectionView!

    var numOfItemsInSecOne: Int!
    override func viewDidLoad() {
        super.viewDidLoad()

        numOfItemsInSecOne = 8
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {

        //print("orientation Changed")
    }

    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return 1
    }

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return numOfItemsInSecOne
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cellO", forIndexPath: indexPath)

        return cell
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize{
    let itemSize = CGSize(width: self.view.frame.width, height: 100)
    return itemSize
    }}
Run Code Online (Sandbox Code Playgroud)

Khu*_*ong 60

添加此功能:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews() 
    myCollection.collectionViewLayout.invalidateLayout()
}
Run Code Online (Sandbox Code Playgroud)

更改方向时,将调用此函数.

  • 使用此功能后,应用程序会在应用程序启动时挂起。 (4认同)
  • 该解决方案效率不高,因为当用户滚动“UICollectionView”的单元格时,会多次调用“viewDidLayoutSubviews()”。 (3认同)
  • UICollectionViewController已经有了`collectionView`属性,所以你就像我说的那样调用它们.只需将它们编辑成`collectionView.reloadData()`即可.它会工作. (2认同)
  • 所以你需要重新加载视图,你不能只是触发布局刷新? (2认同)
  • 检查@kelin的答案`.invalidateLayout()`很有意义。 (2认同)

kel*_*lin 49

更好的选择是调用invalidateLayout()而不是reloadData()因为它不会强制重新创建单元格,因此性能会稍好一些:

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews() 
    myCollection.collectionViewLayout.invalidateLayout()
}
Run Code Online (Sandbox Code Playgroud)

  • 这引起了我的迷恋.当我在viewDidLayoutSubviews函数中放置一个print语句时,会识别出一个无限循环. (7认同)
  • 如果使用UICollectionViewController,则会发生无限循环,因为在这种情况下,collectionView也是控制器的视图。因此,如果更改`collectionView`布局`viewWillLayoutSubviews`,则会调用相关方法。 (3认同)
  • @nyxee,然后尝试`viewWillLayoutSubviews`.我敢打赌,你的Collection View是View Controller的视图吗?如果是这样,我建议将其包装到另一个视图中. (2认同)

bir*_*rdy 14

要更新UICollectionViewLayouttraitCollectionDidChange也可以使用方法:

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)

    guard previousTraitCollection != nil else { return }
    collectionView?.collectionViewLayout.invalidateLayout()
}
Run Code Online (Sandbox Code Playgroud)

  • 出色 - 在自定义 UITableViewCell 中实例化 UICollectionView 时也可以使用。 (3认同)
  • 这对我来说效果最好,所需的方法实际上取决于您的 CollectionView 应用程序,在我的实例中,与其他解决方案相比,这与我的自定义 FlowLayout 的其他行为更兼容,因为我的项目都具有相同的大小,这取决于traitCollection (2认同)
  • 该解决方案不适用于 iPad,因为水平特征和垂直特征没有改变。 (2认同)

小智 12

您也可以通过这种方式使其无效.

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    [self.collectionView.collectionViewLayout invalidateLayout]; 
}
Run Code Online (Sandbox Code Playgroud)


小智 11

UICollectionLayout检测到边界变化时,它会询问是否需要重新路由 Invalidate 布局。您可以直接重写该方法。UICollectionLayout可以invalidateLayout在合适的时间调用方法

class CollectionViewFlowLayout: UICollectionViewFlowLayout{
    
    /// The default implementation of this method returns false.
    /// Subclasses can override it and return an appropriate value
    /// based on whether changes in the bounds of the collection
    /// view require changes to the layout of cells and supplementary views.
    /// If the bounds of the collection view change and this method returns true,
    /// the collection view invalidates the layout by calling the invalidateLayout(with:) method.
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        
        return (self.collectionView?.bounds ?? newBounds) != newBounds
    }
}
Run Code Online (Sandbox Code Playgroud)


The*_*tor 9

viewWillLayoutSubviews()对我不起作用.viewDidLayoutSubviews()也没有.两者都使应用程序进入无限循环,我使用打印命令检查.

其中一种方法是工作

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
// Reload here
}
Run Code Online (Sandbox Code Playgroud)

  • 别忘了叫`super.viewWillTransition ...` (10认同)

Meh*_*hul 9

请理解这一点

traitCollectionDidChange(previousTraitCollection :) 在 iPad 旋转时不会被调用,因为尺寸类在纵向和横向都是 .regular 。只要集合视图大小发生变化,就会调用 viewWillTransition(to:with:) 。

此外,如果您的应用程序支持多任务处理,您不应该使用 UIScreen.mainScreen().bounds,因为它可能不会占据整个屏幕,最好使用 collectionView.frame.width。


小智 9

我的代码:

override func viewWillLayoutSubviews() {
   super.viewWillLayoutSubviews()
   self.collectionView.collectionViewLayout.invalidateLayout()
}
Run Code Online (Sandbox Code Playgroud)

会正常工作!!!


Mic*_*ael 5

呼叫viewWillLayoutSubviews不是最佳选择。首先尝试调用该invalidateLayout()方法。

如果遇到The behaviour of the UICollectionViewFlowLayout is not defined错误,您需要验证视图中的所有元素是否已根据新布局更改了其大小。(请参阅示例代码中的可选步骤)

这是代码,可帮助您入门。根据创建 UI 的方式,您可能需要尝试找到正确的视图来调用该recalculate方法,但这应该会引导您迈出第一步。

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

    super.viewWillTransition(to: size, with: coordinator)

    /// (Optional) Additional step 1. Depending on your layout, you may have to manually indicate that the content size of a visible cells has changed
    /// Use that step if you experience the `the behavior of the UICollectionViewFlowLayout is not defined` errors.

    collectionView.visibleCells.forEach { cell in
        guard let cell = cell as? CustomCell else {
            print("`viewWillTransition` failed. Wrong cell type")
            return
        }

        cell.recalculateFrame(newSize: size)

    }

    /// (Optional) Additional step 2. Recalculate layout if you've explicitly set the estimatedCellSize and you'll notice that layout changes aren't automatically visible after the #3

    (collectionView.collectionViewLayout as? CustomLayout)?.recalculateLayout(size: size)


    /// Step 3 (or 1 if none of the above is applicable)

    coordinator.animate(alongsideTransition: { context in
        self.collectionView.collectionViewLayout.invalidateLayout()
    }) { _ in
        // code to execute when the transition's finished.
    }

}

/// Example implementations of the `recalculateFrame` and `recalculateLayout` methods:

    /// Within the `CustomCell` class:
    func recalculateFrame(newSize: CGSize) {
        self.frame = CGRect(x: self.bounds.origin.x,
                            y: self.bounds.origin.y,
                            width: newSize.width - 14.0,
                            height: self.frame.size.height)
    }

    /// Within the `CustomLayout` class:
    func recalculateLayout(size: CGSize? = nil) {
        estimatedItemSize = CGSize(width: size.width - 14.0, height: 100)
    }

    /// IMPORTANT: Within the `CustomLayout` class.
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {

        guard let collectionView = collectionView else {
            return super.shouldInvalidateLayout(forBoundsChange: newBounds)
        }

        if collectionView.bounds.width != newBounds.width || collectionView.bounds.height != newBounds.height {
            return true
        } else {
            return false
        }
    }
Run Code Online (Sandbox Code Playgroud)