the behavior of the UICollectionViewFlowLayout is

2019-01-13 17:38发布

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 item width must be less than the width of the UICollectionView minus the section insets left and right values, minus the content insets left and right values.
2015-08-18 16:07:51.524 Example[16070:269647] The relevant UICollectionViewFlowLayout instance is , and it is attached to ; animations = { position=; bounds.origin=; bounds.size=; }; layer = ; contentOffset: {0, 0}; contentSize: {1024, 770}> collection view layout: . 2015-08-18 16:07:51.524 Example[16070:269647] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.

This is what I get, what I do is

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 what this happens and if there is a fix for this?

Screenshot

11条回答
啃猪蹄的小仙女
2楼-- · 2019-01-13 18:13

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!

查看更多
唯我独甜
3楼-- · 2019-01-13 18:13

I had the same issue. I fixed this by clearing my collection view of it's constraints, and resetting them in Storyboard.

查看更多
等我变得足够好
4楼-- · 2019-01-13 18:14

This happens when your collection view resizes to something less wide (go from landscape to portrait mode, for example), and the cell becomes too large to fit.

Why is the cell becoming too large, as the collection view flow layout should be called and return a suitable size ?

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

Update to include Swift 4

@objc override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { ... }

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

What happens is that your collection view flow layout subclass does not override the shouldInvalidateLayoutForBoundsChange function, which returns false by default.

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

This means 2 things :

1 - the warning in itself is not harmful

2 - you can get rid of it by simply overriding the shouldInvalidateLayoutForBoundsChange function to return true. In that case, the flow layout will always be called when the collection view bounds change.

查看更多
姐就是有狂的资本
5楼-- · 2019-01-13 18:21

After doing some experimenting, this seems to also be tied to how you layout your collectionView.

The tl;dr is: Use AutoLayout, not autoresizingMask.

So for the core of the problem the best solutions I've found to handle orientation change use the following code, which all 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 still situations where you can get the item size warning. For me it's if I am in landscape in one tab, switch to another tab, rotate to portrait, then return to the previous tab. I tried invalidating layout in willAppear, willLayout, all the usual suspects, but no luck. In fact, even if you call invalidateLayout before super.willAppear() you still get the warning.

And ultimately, this problem is tied to the size of the collectionView bounds updating before the delegate is asked for the itemSize.

So with that in mind I tried using AutoLayout instead of collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight], and with that, problem solved! (I use SnapKit to do AutoLayout so that I don't pull my hair out constantly). You also just need to invalidateLayout in viewWillTransition (without the coordinator.animate, so just as you have in your example), and also invalidateLayout at the bottom of viewWillAppear(:). That seems to cover all situations.

I dont know if you are using autoresizing or not - it would be interesting to know if my theory/solution works for everyone.

查看更多
啃猪蹄的小仙女
6楼-- · 2019-01-13 18:23

I've also seen this occur when we set the estimatedItemSize to automaticSize and the computed size of the cells is less than 50x50 (Note: This answer was valid at the time of iOS 12. Maybe later versions have it fixed).

i.e. if you are declaring support for self-sizing cells by setting the estimated item size to the automaticSize constant:

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

and your cells are actually smaller than 50x50 points, then UIKit prints out these warnings. FWIW, the cells are eventually sized appropriately and the warnings seem to be harmless.

One fix workaround is to replace the constant with a 1x1 estimated item size:

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

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

Setting it to any other value causes the collection view to query each cell for its actual size using the cell’s preferredLayoutAttributesFitting(_:) method.

However, it might/might-not have performance implications.

查看更多
老娘就宠你
7楼-- · 2019-01-13 18:32

Its happens because your collection view cell's width is bigger than collection view width after rotation.suppose that you have a 1024x768 screen and your collection view fills the screen.When your device is landscape,your cell's width will be self.collectionView.frame.size.width - 20 =1004 and its greater than your collection view's width in portrait = 768.The debugger says that "the item width must be less than the width of the UICollectionView minus the section insets left and right values, minus the content insets left and right values".

查看更多
登录 后发表回答