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的主要内容,如果未能解决你的问题,请参考以下文章
-[UICollectionView _endItemAnimations] 中的 UICollectionView 断言失败
如何滚动另一个 UICollectionView 下方的 UICollectionView?
UICollectionView 单元内部已加载,但 UICollectionView 未显示
UICollectionView 断言失败 -[UICollectionView _updateWithItems:tentativelyForReordering:]