UICollectionView invalidateLayout 不会在屏幕方向更改时触发 sizeForItem

Posted

技术标签:

【中文标题】UICollectionView invalidateLayout 不会在屏幕方向更改时触发 sizeForItem【英文标题】:UICollectionView invalidateLayout doesn't fire the sizeForItem on screen orientation change 【发布时间】:2017-10-31 22:18:39 【问题描述】:

我有一个 UICollectionViewController 有一堆单元格,每个单元格都有屏幕大小。

如果我以纵向或横向启动应用程序,一切都会按预期工作。

问题是当我更改屏幕方向并调用collectionViewLayout.invalidateLayout() 时,单元格大小保持不变,而没有调整它们的大小。

我不知道为什么会这样。

这里有一些代码:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 
    print("size for item")
    return CGSize(width: view.frame.width, height: view.frame.height)



override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) 
    coordinator.animate(alongsideTransition:  (_) in
        self.collectionViewLayout.invalidateLayout() // layout update
    , completion: nil)

在第一次加载时,我从设置的尺寸中获取打印件,但在更改方向后,它不会调用它,所以当我更改方向时,单元格大小不适合屏幕,因为它适合启动。

任何想法可能是什么原因造成的,或者我在这里做错了什么?

谢谢

编辑

不知何故,我试图在willTransition 函数中插入一个打印语句,但我注意到它不会在屏幕旋转时触发。我认为问题出在invaldateLayout() 之外,因为它从不触发。

这个覆盖函数怎么不自动调用呢?

【问题讨论】:

【参考方案1】:

UICollectionViewFlowLayoutInvalidationContext 上有一个专用标志,称为invalidateFlowLayoutDelegateMetrics。文档内容如下:

此属性的默认值为 false。如果您因更改任何项目的大小而使布局无效,请将此属性设置为 true。 当此属性设置为 true 时,流布局对象会重新计算其项目和视图的大小,并根据需要查询委托对象以获取该信息。

在流布局子类中升旗。

class CollectionViewFlowLayout: UICollectionViewFlowLayout 
        
    override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext 
        let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext
        if let collectionView = collectionView 
            context.invalidateFlowLayoutDelegateMetrics = collectionView.bounds.size != newBounds.size
        
        return context
    

【讨论】:

太棒了!网络上很少有人提到这一点。我将它与shouldInvalidateLayout(forBoundsChange newBounds: CGRect)结合使用【参考方案2】:

我可以推荐的是覆盖布局类并实现shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool

class CollectionViewLayoutSelfSizingCells : UICollectionViewFlowLayout 
    
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool 
        if let collectionView = self.collectionView 
            return collectionView.frame.size != newBounds.size
        

        return false
    

这是我找到的最好的解决方案,它适用于整个项目。 老实说,我很惊讶UICollectionViewFlowLayout不是默认设置

【讨论】:

很酷的东西,也可以通过扩展名extension UICollectionViewFlowLayout open override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool @AlexandreG 我很确定你不想通过扩展来做到这一点,因为它会改变你在应用程序中拥有的所有布局的行为——这不是你想要的。【参考方案3】:

您应该实现func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) 方法。每当视图控制器的大小发生变化时都会调用此方法,例如在纵向和横向之间旋转时:

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

    coordinator.animate(alongsideTransition:  (_) in
        self.collectionViewLayout.invalidateLayout() // layout update
    , completion: nil)

【讨论】:

天啊,你是对的。我混淆了委托方法。正如您所提到的,当我应该使用 viewWillTransition 时,我正在使用 willTransition。谢谢:D

以上是关于UICollectionView invalidateLayout 不会在屏幕方向更改时触发 sizeForItem的主要内容,如果未能解决你的问题,请参考以下文章

ResourceLoader返回空字符串

折叠UICollectionView

-[UICollectionView _endItemAnimations] 中的 UICollectionView 断言失败

如何滚动另一个 UICollectionView 下方的 UICollectionView?

UICollectionView 单元内部已加载,但 UICollectionView 未显示

UICollectionView 断言失败 -[UICollectionView _updateWithItems:tentativelyForReordering:]