2015-08-18 16:07:51.523 Example[16070:269647] the behavior of the UICollectionViewFlowLayout is not defined because: 2015-08-18 16:07:51.523
Example
This works for me:
Invalidate the layout on rotation:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}
Have a separate function for calculating the available width:
Note: taking in to account both section inset and content inset.
// MARK: - Helpers
func calculateCellWidth(for collectionView: UICollectionView, section: Int) -> CGFloat {
var width = collectionView.frame.width
let contentInset = collectionView.contentInset
width = width - contentInset.left - contentInset.right
// Uncomment the following two lines if you're adjusting section insets
// let sectionInset = self.collectionView(collectionView, layout: collectionView.collectionViewLayout, insetForSectionAt: section)
// width = width - sectionInset.left - sectionInset.right
// Uncomment if you are using the sectionInset property on flow layouts
// let sectionInset = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionInset ?? UIEdgeInsets.zero
// width = width - sectionInset.left - sectionInset.right
return width
}
And then of course finally returning the item size:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: calculateCellWidth(for: collectionView, section: indexPath.section), height: 60)
}
I think it is important to note that this works because invalidating a layout wont trigger a recalculation of the cell size immediately, but during the next layout update cycle. This means that once the item size callback is eventually called, the collection view has the correct frame, thus allowing accurate sizing.
Voila!
I found that this worked quite well for UICollectionViewController
and animated correctly:
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
// Ensure the layout is within the allowed size before animating below, or
// `UICollectionViewFlowLayoutBreakForInvalidSizes` breakpoint will trigger
// and the logging will occur.
if ([self.collectionView.collectionViewLayout isKindOfClass:[YourLayoutSubclass class]]) {
[(YourLayoutSubclass *)self.collectionView.collectionViewLayout updateForWidth:size.width];
}
// Then, animate alongside the transition, as you would:
[coordinator animateAlongsideTransition:^
(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[self.collectionView.collectionViewLayout invalidateLayout];
}
completion:nil];
[super viewWillTransitionToSize:size
withTransitionCoordinator:coordinator];
}
///////////////
@implementation YourLayoutSubclass
- (void)prepareLayout {
[super prepareLayout];
[self updateForWidth:self.collectionView.bounds.size.width];
}
- (void)updateForWidth:(CGFloat)width {
// Update layout as you wish...
}
@end
... See inline code comments above for explanation.
For Xcode 11, none of the above methods worked for me.
It turned out one need to set the Estimate Size to None in the collection view size panel.
see: https://forums.developer.apple.com/thread/123625
You can check in debugger if collectionView.contentOffset
is changed to negative in my case it changes from (0,0)
to (0,-40)
. You can solve this issue by using this method
if ([self respondsToSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)]) {
self.automaticallyAdjustsScrollViewInsets = NO;
}
This is what you need:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
//Get frame width
let width = self.view.frame.width
//I want a width of 418 and height of 274 (Aspect ratio 209:137) with a margin of 24 on each side of the cell (See insetForSectionAt 24 + 24 = 48). So, check if a have that much screen real estate.
if width > (418 + 48) {
//If I do return the size I want
return CGSize(width: 418, height: 274)
}else{
//Get new width. Frame width minus the margins I want to maintain
let newWidth = (width - 48)
//If not calculate the new height that maintains the aspect ratio I want. NewHeight = (originalHeight / originalWidth) * newWidth
let height = (274 / 418) * newWidth
//Return the new size that is Aspect ratio 209:137
return CGSize(width: newWidth, height: height)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 24, left: 24, bottom: 24, right: 24)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 33
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 33
}
XCode 11
For me, the reason was that the CollectionView's Estimate option size was set to Automatic. It seems it is the default behavior in XCode 11.
Hence for me which I loaded the images in the cells, because of the size of the images and the automatic option (which wants to adapt the size of the cell with the size of the image) the cell's width became greater than the CollectionView's width.
By setting this option to None the problem solved.