Why does UICollectionView log an error when the cells are fullscreen?
Asked Answered
W

13

47

I have a UICollectionViewController using a UICollectionViewFlowLayout where my itemSize is the size of the UICollectionView. Basically, this is a line layout of cells where each cell is fullscreen and scrolls horizontally.

In my UICollectionViewFlowLayout subclass, I have overridden prepareLayout as follows:

- (void)prepareLayout {
    self.itemSize = self.collectionView.frame.size;
    self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    self.collectionView.pagingEnabled = YES;
    self.minimumLineSpacing = 0.0;
    self.minimumInteritemSpacing = 0.0;
    self.sectionInset = UIEdgeInsetsZero;
    self.footerReferenceSize = CGSizeZero;
    self.headerReferenceSize = CGSizeZero;
}

The UICollectionViewController is very basic returning 10 items in one section. I've included a sample project on GitHub for more detail.

Everything appears to be set up correctly. It looks right in the simulator and on the device but, when the collection view is displayed, there is an error logged to the console:

the behavior of the UICollectionViewFlowLayout is not defined because:
the item height must be less that the height of the UICollectionView minus the section insets top and bottom values.

Note also that the collection view controller in my example is in a navigation controller and while that doesn't look particularly necessary in the example, in my real-world case I need the collection view in a navigation controller.

Whitsunday answered 11/5, 2014 at 18:11 Comment(1)
if you want to keep the scrollViewInsets managed then just return the collecitonView height and width minus the content inset and safeAreaInsets. So height = collectionView.frame.size.height - collectionView.contentInset.top - collectionView.safeAreaInsets.top - collectionView.safeAreaInsets.bottom and same with width.Agrapha
W
79

There is a property on UIViewControllerautomaticallyAdjustsScrollViewInsets–that defaults to YES. This means that when a UIViewController has a UIScrollView in its view hierarchy–which is true of a UICollectionViewController–the contentInset property of that scroll view is adjusted automatically to account for screen areas consumed by the status bar, navigation bar, and toolbar or tab bar.

The documentation for that property states:

automaticallyAdjustsScrollViewInsets

Specifies whether or not the view controller should automatically adjust its scroll view insets.

@property(nonatomic, assign) BOOL automaticallyAdjustsScrollViewInsets

Discussion

Default value is YES, which allows the view controller to adjust its scroll view insets in response to the screen areas consumed by the status bar, navigation bar, and toolbar or tab bar. Set to NO if you want to manage scroll view inset adjustments yourself, such as when there is more than one scroll view in the view hierarchy.

The solution is to set automaticallyAdjustsScrollViewInsets to NO somewhere in your UICollectionViewController subclass, such as in viewDidLoad:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.automaticallyAdjustsScrollViewInsets = NO;
}

I have put an example project on GitHub that illustrates this problem and solution. There are two branches: with_error and fixed_error. Here is a diff of the change on GitHub.

Whitsunday answered 11/5, 2014 at 18:11 Comment(8)
I'd have made it "up" twice if I could. Also, NOTE that you can change automaticallyAdjustsScrollViewInsets right in the Interface Builder.Internuncial
damn...thanks for that. I was really hitting my head hard!Berdichev
My collection view is one of a table view cell's subviews, this solution is not working for my situation.Privative
@Internuncial I can't see such an option in interface builder, I am using UICollectionView in UIViewController.Geibel
@Geibel Select your ViewController. In the Inspector it's "Attributes" tab where you will find it under "View Controller" sectionInternuncial
Thanks @nikans, yeah I found that, but still the same problem.Geibel
I face this problem only when I load image.Geibel
automaticallyAdjustsScrollViewInsets is deprecated. Use contentAdjustmentBehaviour on the scrollView instead.Manichaeism
E
20

iOS 11 update: automaticallyAdjustsScrollViewInsets is deprecated in iOS 11.0.

Apple recommends using UIScrollView's contentInsetAdjustmentBehavior method instead. I set this value to .never and the error has gone. You can also set this property in Interface Builder.

collectionView.contentInsetAdjustmentBehavior = .never
Elytron answered 2/11, 2018 at 11:13 Comment(1)
I tried this and it is a hack that causes the cells to animate in funny ways while scrolling. I do not recommend this. See my answer for what worked for me.Curtate
J
17

This issue just occured to me on 3x screens (namely the iPhone 6 Plus.) As it turned out, the autolayout engine did not really like long floating point values (such as .33333333), so my solution was to floor the return height in sizeForItemAt:indexPath:.

return CGSize(width: preferredWidth, height: floor(preferredHeight))
Jones answered 19/6, 2017 at 9:29 Comment(3)
Interesting case. Maybe then it makes sense to also apply floor to width?Elytron
@Elytron Yes, that's probably a good idea in other cases, but in this question, the cells are full width and the width of the screen in logical pixels is always a whole number.Zugzwang
Legendary answer... fixed it for me, thanks!Chur
C
6

I encountered this problem when rotating the device from portrait to landscape, back to portrait. You want to invalidate the collectionView's layout upon device rotation and before the call to super, like so:

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    // Causes collection view cells to be resized upon orientation change.
    // Important that this is called *before* call to super in order to prevent error from being logged to console.
    [self.collectionView.collectionViewLayout invalidateLayout];

    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    //...
}
Curtate answered 16/12, 2018 at 21:50 Comment(0)
T
5

This way had worked for me perfectly!.

I just subtracted the top and bottom insets from the view's height as said in that error.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: view.frame.width , height: view.frame.height - (view.safeAreaInsets.top + view.safeAreaInsets.bottom))
}

I hope it helps!

Tit answered 13/9, 2019 at 10:29 Comment(1)
Be careful - this code would only work fine of the UICollectionView takes the whole space of self.viewElytron
B
5

If you have collectionView inside scrollView just put .invalidateLayout method inside viewDidLayoutSubviews as shown below:

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

I found out that .invalidateLayout method inside viewWillTransitionToSize doesn't change collection view bounds on orientation change in some cases.

Bertolde answered 10/12, 2020 at 10:39 Comment(0)
M
5

A fix that worked for me.

collectionViewLayout.estimatedItemSize = .zero

or do it via IB:

Estimated Size: None

If you create the collection view in the IB, the Estimated Size property (estimatedItemSize) is set to Auto. The docs say it's .zero by default but it's not.

Meshach answered 16/12, 2020 at 11:52 Comment(0)
E
2

Like Stunner, I had the same problem when rotating from landscape (picture using full width) to portrait mode. His suggestion was the only one which really helped.

Attached the code with latest Swift for his ObjC example ... and as a bonus, my code to find the center cell of the collection view. Works quite nice ;-)

/**
 -----------------------------------------------------------------------------------------------

 viewWillTransition()

 -----------------------------------------------------------------------------------------------
 */
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {

    // find cell in the center of the screen and store it in a global variable
    let center = self.view.convert((self.collectionView!.center), to: self.collectionView)

    // get the indexPath for the cell in the center of the current screen
    if let index = collectionView!.indexPathForItem(at: center) {

        // store it in a class property
        self.indexPathOfCenterCell = index
    }

    // force recalculation of margins, borders, cell sizes etc.
    self.collectionView?.collectionViewLayout.invalidateLayout()

    // inform UIKit about it    
    super.viewWillTransition(to: size, with: coordinator)
}
Equidistant answered 2/7, 2019 at 20:34 Comment(0)
G
1

ios 10: topmost view was not connected to the view outlet

Gao answered 12/6, 2017 at 12:33 Comment(0)
A
1

I was getting the same error when I was trying to embed a UICollectionView in a UITableView. I was generating a new UICollectionView in each UITableView cell, but I did not put any constraints on the height of that UICollectionView. So, when I put a constraint on the height, that error is gone!

Avicenna answered 22/2, 2020 at 3:57 Comment(1)
If someone else would try to achieve a similar UI behaviour, I would recommend to take a look at Compositional Layout introduced at WWDC 2019: developer.apple.com/documentation/uikit/…. Even though it's available only for iOS 13+, you can still use this wrapper to make it available for older iOS versions: github.com/kishikawakatsumi/…Elytron
T
1

In my case I had property

layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

set in my flow layout. So I had to switch it to some obviously wrong constant (but with some explicit height) to suppress this warning.

UICollectionView is such a thing-in-itself and sometimes absolutely unpredictable

Trichosis answered 31/10, 2022 at 8:39 Comment(0)
C
0

I had similar issue.

After load cell which is full width and some height of screen. on some condition I changed the height of cell then I was getting the same error

to fix this

I used

   func updateHeightPerRatio(with image:UIImage) {
    let ratio = collectionView.bounds.width / image.size.width
    constHeightCollectionView .constant =  ceil(image.size.height * ratio)
    collectionView.reloadData()
    collectionView.performBatchUpdates({
        collectionView.layoutIfNeeded()
    }) { (completed) in
        self.collectionView.reloadData()
        self.layoutIfNeeded()
    }
}

Solution is reload data then perform batchupdate with that collection view re -calculate the frames . after that reload collectionview again it will apply calculated frames to cell

And now there is no log for issue now.

Hope it is helpful

Catastrophism answered 17/8, 2018 at 6:47 Comment(0)
K
0

In my case I have to reduce bottom inset (from 20 to 0) of cell as I have reduced 20 from height of collectionview From

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
-        return UIEdgeInsets(top: 10, left: 10, bottom: 20, right: 10)
+        return UIEdgeInsets(top: 10, left: 10, bottom: 0, right: 10)
     }

func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {
        let size = CGSize(width: 350, height: collectionView.bounds.size.height - 20)
        return size
    }
Kegler answered 25/2, 2020 at 10:44 Comment(1)
I bet that in yours case it would be possible to make the height of the items to be up to collectionView.bounds.size.height - 30. Note that before making the change the total vertical inset of content was 10 + 20 = 30. After the change it's 10 + 0 = 10 - so no magic here :)Elytron

© 2022 - 2024 — McMap. All rights reserved.