UICollectionView的invalidateLayout方法在屏幕方向改变时不会触发sizeForItem。

4

我有一个 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函数中插入一个print语句,并注意到它不会在屏幕旋转时触发。我认为问题应该不是invaldateLayout()而是在那里。因为它从来没有触发过。
这个覆盖函数怎么可能不被自动调用呢?
3个回答

12
在`UICollectionViewFlowLayoutInvalidationContext`上有一个专门的标志,称为`invalidateFlowLayoutDelegateMetrics`。文档中写道:
默认值为false。如果您由于任何项目的大小更改而使布局无效,请将此属性设置为true。 当此属性设置为true时,流布局对象会重新计算其项目和视图的大小,并根据需要查询委托对象以获取该信息。
在流布局子类中提高标志。
override open func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
    guard let context = super.invalidationContext(forBoundsChange: newBounds) as? UICollectionViewFlowLayoutInvalidationContext else {
        return UICollectionViewLayoutInvalidationContext()
    }
    guard let collectionView = collectionView else {
        return context
    }
    context.invalidateFlowLayoutDelegateMetrics = collectionView.bounds.size != newBounds.size
    return context
}

1
太好了!网络上很少有人提到这个。我将它与 shouldInvalidateLayout(forBoundsChange newBounds: CGRect) 结合使用。 - Husam

4
我可以推荐的是重写布局类并实现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 { - Alexandre G
3
我很确定你不想通过扩展来实现这一点,因为这将改变应用程序中所有布局的行为,而这并不是你想要的。 - Grzegorz Krukowski

2

您需要实现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 - Ivan Cantarino

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