The behavior of UICollectionViewFlowLayout is undefined because the width of the cell is greater than the width of the View collection

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 [16070: 269647] the width of an element should be less than the width of the UICollectionView minus the insertion sections and the correct values, minus inserting the contents left and right.
2015-08-18 16: 07: 51.524 Example [16070: 269647] The corresponding Instance is UICollectionViewFlowLayout, and it is bound to; animation = {position =; bounds.origin =; bounds.size =; }; layer =; contentOffset: {0, 0}; contentSize: {1024, 770}> collection layout :. 2015-08-18 16: 07: 51.524 Example [16070: 269647] Make a symbolic breakpoint in UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.

This is what I get, what I do, this

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { return CGSizeMake(self.collectionView!.frame.size.width - 20, 66) } 

when I rotate from landscape to portrait, the console shows this error message only in iOS 9, does anyone know that this is happening, and if there is a fix for this?

Screenshot

+33
ios9 uicollectionview uicollectionviewcell uicollectionviewlayout xcode7-beta5
Aug 18 '15 at 21:18
source share
12 answers

This happens when viewing the collection changes to a lower one (for example, switching from landscape to portrait mode) and the cell becomes too large to fit.

Why is the cell getting too big because I need to display the layout of the collection view stream and return the appropriate size?

collectionView (collectionView: UICollectionView, layout collectionLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath)

Update to enable Swift 4

@objc override func collectionView (_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) β†’ UICollectionViewCell {...}

This is because this function is called not, or at least not immediately.

What happens is that a subclass of the collection view view class class does not override the shouldInvalidateLayoutForBoundsChange function, which returns false by default.

When this method returns false, the collection view first tries to jump with the current cell size, detects a problem (which logs a warning), and then calls the flow layout to resize the cell.

This means 2 things:

1 - warning in itself is not harmful

2 - you can get rid of it by simply overriding the shouldInvalidateLayoutForBoundsChange function to return true. In this case, the stream layout will always be called when the boundaries of the collection view are changed.

+25
Sep 14 '16 at 19:03
source share
β€” -

This is because the cell width of the collection view is larger than the width of the collection view after rotation. Suppose you have a 1024x768 screen and your collection view fills the screen. When your device is terrain, your cell width will be self.collectionView. frame.size.width is 20 = 1004, and its width is greater than the width of the collection in the portrait = 768. The debugger says that "the width of the element should be less than the width of the UICollectionView minus the insertion values ​​of the left and right, minus the insertion of content left and right" .

+5
Dec 30 '15 at 16:50
source share

I use a subclass of UICollectionViewFlowLayout and use its itemSize property to specify the size of the cell (instead of collectionView:sizeForItemAtIndexPath: delegate method). Each time I rotate the screen a shorter width (e.g. landscape β†’ portrait), I get this huge warning.

I was able to fix this by following 2 steps.

Step 1: In the UICollectionViewFlowLayout subclass of the prepareLayout() function, move super.prepareLayout() so that after where self.itemSize set. I think this forces the superclass to use the correct itemSize value.

 import UIKit extension UICollectionViewFlowLayout { var collectionViewWidthWithoutInsets: CGFloat { get { guard let collectionView = self.collectionView else { return 0 } let collectionViewSize = collectionView.bounds.size let widthWithoutInsets = collectionViewSize.width - self.sectionInset.left - self.sectionInset.right - collectionView.contentInset.left - collectionView.contentInset.right return widthWithoutInsets } } } class StickyHeaderCollectionViewLayout: UICollectionViewFlowLayout { // MARK: - Variables let cellAspectRatio: CGFloat = 3/1 // MARK: - Layout override func prepareLayout() { self.scrollDirection = .Vertical self.minimumInteritemSpacing = 1 self.minimumLineSpacing = 1 self.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: self.minimumLineSpacing, right: 0) let collectionViewWidth = self.collectionView?.bounds.size.width ?? 0 self.headerReferenceSize = CGSize(width: collectionViewWidth, height: 40) // cell size let itemWidth = collectionViewWidthWithoutInsets self.itemSize = CGSize(width: itemWidth, height: itemWidth/cellAspectRatio) // Note: call super last if we set itemSize super.prepareLayout() } // ... } 

Note that the above change will somehow resize the layout when screen rotation fails. This is where step 2 comes in.

Step 2: Put this in the view controller, which contains the collection view.

  override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator) collectionView.collectionViewLayout.invalidateLayout() } 

Now the warning has passed :)




Some notes:

  1. Make sure you add constraints to collectionView and not use collectionView.autoresizingMask = [.flexibleWidth,.flexibleHeight]

  2. Make sure you do not call invalidateLayout in viewWillTransitionToSize() because the width of the cell from edge to edge in landscape viewWillTransitionToSize() greater than the frame width of the collections of views in portrait orientation. See links below.

Recommendations

+5
Mar 18 '16 at 5:57
source share

I solved this problem using safeAreaLayoutGuide .

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: (view.safeAreaLayoutGuide.layoutFrame.width), height: 80); } 

and you must also override this function to properly support portrait and landscape mode.

  override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { collectionView?.collectionViewLayout.invalidateLayout(); } 
+4
Nov 30 '17 at 22:44
source share

You can check the debugger if collectionView.contentOffset changed to negative, in my case it changes from (0,0) to (0,-40) . You can solve this problem using this method.

 if ([self respondsToSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)]) { self.automaticallyAdjustsScrollViewInsets = NO; } 
+3
Sep 25 '16 at 8:46
source share

After some experimentation, this also seems to be related to how you compose your CollectionView.

Tl; dr: Use AutoLayout, not autoresizingMask.

So, at the heart of the problem, the best solutions that I found for handling orientation changes use the following code, which makes sense:

 override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate(alongsideTransition: { (context) in self.collectionView.collectionViewLayout.invalidateLayout() }, completion: nil) } 

However, there are situations where you may receive a warning about the size of an element. For me, if I am in the landscape on one tab, switch to another tab, rotate to portrait and return to the previous tab. I tried to invalidate the layout in willAppear, willLayout, all the usual suspects, but no luck. In fact, even if you call invalidateLayout before super.willAppear() , you will still get a warning.

And ultimately, this problem is tied to the size of updating the boundaries of the collection before the delegate asks for the itemize element.

So, keeping in mind, I tried to use AutoLayout instead of collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] , and this solved the problem! (I use SnapKit to do AutoLayout so I don't pull my hair out all the time). You also just need invalidateLayout in viewWillTransition (without coordinator.animate , just like in your example), as well as invalidateLayout at the bottom of viewWillAppear(:) . This is like all situations.

I don't know if you use autoresist or not - it would be interesting to know if my theory / solution works for everyone.

+2
Dec 19 '16 at 10:11
source share

This works for me:

Invalid layout during rotation:

 override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() collectionView.collectionViewLayout.invalidateLayout() } 

There is a separate function for calculating the available width:

Note: taking into account both the insertion of a section and the insertion of content.

 // 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 finally, finally, we return the size of the item:

 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’s important to note that this works because canceling the layout will not immediately recalculate the cell size, but during the next layout update cycle. This means that as soon as the element size callback is ultimately invoked, the collection view has the correct frame, thus allowing accurate sizing.

Voila!

+2
Sep 18 '18 at 8:42
source share

I also saw how this happens when we set the valuesItemSize parameter to AutomaticSize, and the calculated cell size is less than 50x50 (Note: This answer was valid in iOS 12. Perhaps it was fixed in later versions).

that is, if you declare support for self-sized cells by setting the estimated size of the element to automaticSize constant:

 flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize 

and your cells are actually less than 50x50 dots, then UIKit prints these warnings. Unfortunately, the cells ultimately have the appropriate size, and the warnings seem harmless.

One workaround for fixing this is to replace the constant with the element size of 1x1:

 flowLayout.estimatedItemSize = CGSize(width: 1, height: 1) 

This does not affect self-sizing support, because as the documentation estimatedItemSize mentions:

Setting it to any other value causes the collection view to request each cell for its actual size, using the cell's preferred elementayLayoutAttributesFitting (_ :).

However, this may / may not have performance implications.

+2
Oct 25 '18 at 15:48
source share

I had a similar problem.

In my case, I got a collection view, and when you knocked on one of the cells, a popover with a UITextField opened for editing the element. After this popover disappeared, self.collectionView.contentInset.bottom was set to 55 (initially 0).

To fix my problem, after the popover view disappears, I manually configure the contents of the UIEdgeInsetsZero to UIEdgeInsetsZero .

contextual prediction bar

The original problem seems to be related to the contextual prediction panel that appears at the top of the keyboard. When the keyboard is hidden, the panel disappears, but the value of contentInset.bottom does not return to its original value.

Since your problem seems to be related to the width, not the height of the cell, check to see if any of the contentInset or layout.sectionInset values contentInset layout.sectionInset you set.

+1
Sep 30 '15 at 10:47
source share

I had the same problem. I fixed this by clearing my view of the collection of its limitations and dropping them into the Storyboard.

+1
Jun 10 '17 at 16:26
source share

I found that this works well for the UICollectionViewController and it animates 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 an explanation. πŸ‘πŸ»

0
Jan 27 '19 at 9:02
source share

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 } 
-3
Aug 13 '17 at 0:30
source share



All Articles