1

I am building a collection view (gallery) of images.

The layout is 3 cells per row.

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

        let width: CGFloat = (view.frame.width / 3) - 8
        let height: CGFloat = width

        return CGSize(width: width, height: height)
    }

Everything looks great until I scroll to the bottom of the collection view.

it goes from: 3 per row, looks good

to: super blown up picture, larger than the screen

I also get this error message:

The behavior of the UICollectionViewFlowLayout is not defined because:

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. 

Please check the values returned by the delegate.

The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x7fa97f708c70>, and it is attached to <UICollectionView: 0x7fa981022000; frame = (0 0; 414 896); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x6000019986f0>; layer = <CALayer: 0x6000017a8820>; contentOffset: {0, 498.33333333333331}; contentSize: {414, 1250}; adjustedContentInset: {88, 0, 34, 0}; layout: <UICollectionViewFlowLayout: 0x7fa97f708c70>; dataSource: <MarsRover.GalleryCollectionVC: 0x7fa97f50b610>>.

any insight would be great, I want to turn this into infinite scrolling (api prefetching) too down the road, just fyi if that means I can ignore this.

Peter Irving
  • 305
  • 2
  • 10
  • Have you tried to change your collection view item width calculation to base it on the `collectionView` instead of the `view.frame.width`? Something like: `let width: CGFloat = (collectionView.frame.width / 3) - 8`. – Orlando Nov 07 '19 at 21:38
  • thanks, Orlando, but I've tried view.bounds.width, view.frame.width, view.frame.size.width, cv.frame.size.width, cv.frame.width, etc... – Peter Irving Nov 07 '19 at 22:27

3 Answers3

1

The error you're getting is pointing at the item's width as the problem, it is saying, basically that the item(s) width and spacing cannot be more than the collection view's width. This is because your collection view has a vertical scrolling.

So to achieve a correct behavior for your collection view you must set your controller as the delegate for the collection view and also adopt the UICollectionViewDelegateFlowLayout. In your case I can see you've already implemented the collectionView(_:, layout:, sizeForItemAt: method.

In your implementation there is a clear intention to divide the collectionView's width in three equal parts. The calculation is considering a third part of the self.view.width minus eight. If I assume correctly you're intending to left an inter-item spacing of 8. If that's the case you should specify it into another method:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
    return 8
}

That will specify the inter-item spacing to 8 points.

Continuing with the cell's width and height, you must then divide the collectionView.frame.width but before that you must subtract the inter-spacing value from this quantity, because that is the remaining space for your cells.

So your implementation would be

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    // We subtract 16 because there are 2 inter item spaces like:
    // [CELL] [spacing:8] [CELL] [spacing:8] [CELL]
    let usableWidth = collectionView.frame.width - (2 * 8) 

    let cellWidth: CGFloat = usableWidth / 3
    let cellHeight: CGFloat = cellWidth

    return CGSize(width: cellWidth, height: cellHeight)
}

This should do the layout for you.

Orlando
  • 1,509
  • 2
  • 19
  • 27
0

Since all of your items are they same size you should not be setting them in the delegate method. Instead set a static size on your flowLayout (you can drag an outlet from the storyboard) in viewDidLayoutSubviews and use the layout insets to do it so you can't possibly get the math wrong:

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        let remainingSpace = collectionView.bounds.width
                            - flowLayout.sectionInset.left
                            - flowLayout.sectionInset.right
                            - flowLayout.minimumInteritemSpacing * (Constant.numberOfItemsAcross - 1)
        let dimension = remainingSpace / Constant.numberOfItemsAcross
        flowLayout.itemSize = CGSize(width: dimension, height: dimension)
    }
Josh Homann
  • 15,933
  • 3
  • 30
  • 33
0

First of all, why not use bounds instead of frame?

Second, the reason this is happening is most likely because you are adjusting the layout elsewhere while the collectionView is loading cells (scrolling will make it load cells).

Are you using UIScrollViewDelegate methods to do anything with layout?