Reputation: 3246
I have a UICollectionViewController that has a bunch of cells, and each cell has the screen size.
Everything works as expected if I launch the app either in portrait or in landscape.
The problem is when I change the screen orientation, and I call collectionViewLayout.invalidateLayout()
the cells size stays as it was before, without resizing them.
I don't know why this is happening.
Here's some code:
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)
}
On the first load, I get the prints from the size set, but after changing orientation, it doesn't call it, so when I change orientation the cells size doesn't fit the screen as it fits on launch.
Any ideia what maybe causing this or what am I doing wrong here?
Thank you
EDIT
Somehow I tried to insert a print statement inside the willTransition
function and I noticed it doesn't fire on screen rotation. I think the problem is there instead of the invaldateLayout()
since it never fires.
How can this override function not be automatically called?
Upvotes: 5
Views: 6402
Reputation: 2868
There is a dedicated flag for that on UICollectionViewFlowLayoutInvalidationContext
called invalidateFlowLayoutDelegateMetrics
. The documentation reads:
The default value of this property is false. Set this property to true if you are invalidating the layout because of changes to the size of any items. When this property is set to true, the flow layout object recomputes the size of its items and views, querying the delegate object as needed for that information.
Raise the flag in flow layout subclass.
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
}
Upvotes: 14
Reputation: 318774
You should implement the func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
method. This is called whenever the view controller's size will change, such as when rotating to/from portrait and landscape:
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)
}
Upvotes: 2
Reputation: 19792
What I can recommend is overriding the layout class and implementing 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
}
}
This is nicest solution I found and it's working across whole project.
To be honest I was quite surprised that is not default on UICollectionViewFlowLayout
Upvotes: 4