iosswiftexceptionuicollectionviewlayoutuicollectionviewdelegateflowlayout

Exception in collectionView(_:layout:sizeForItemAt:) when accessing layout.collectionViewContentSize


Achieve

I want to have two different item sizes in a UICollectionView. Therefore I've implemented

collectionView(_:layout:sizeForItemAt:) of UICollectionViewDelegateFlowLayout.

Error

But I'm facing the problem that my app crashes with an exception whenever I access collectionViewLayout.collectionViewContentSize in this function.

The exception is the following:

* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM objectAtIndexedSubscript:]: index 1 beyond bounds [0 .. 0]'.

Throw call stack is at the bottom of the post.

It seems that it is an internal problem of the SDK and probably something isn't initialised properly!?

Question

Can anyone confirm this error or tell me what I'm doing wrong?

Implementation

Here is my implementation of the UICollectionViewDelegateFlowLayout:

class TwoSizesCellFlowLayout: NSObject, UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let layoutSize = collectionViewLayout.collectionViewContentSize // CRASHES when accessing `collectionViewContentSize`
        // SDK error?
        let layoutSize = collectionView.contentSize // works but results in slightly different size
        var size = layoutSize.width / 2

        if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout {
            size = size - flowLayout.minimumInteritemSpacing / 2
        }
        return CGSize(width: size, height: size)
    }
}

In the collection view controller subclass I assign an object of TwoSizesCellFlowLayout to the delegate property of UICollectionViewController:

class ViewController: UICollectionViewController {

    ...

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        collectionView?.dataSource = self
        collectionView?.delegate = collectionViewDelegateAndLayout

        let nib = UINib.init(nibName: "BasicCollectionViewCell", bundle: nil)
        collectionView?.register(nib, forCellWithReuseIdentifier: cellIdentifier)

        // accessing `collectionViewContentSize` here works like a charm, but only one size for all items is possible
//        let collectionViewSize = collectionView?.collectionViewLayout.collectionViewContentSize
//        if let collectionSize = collectionViewSize, let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
//            let size = collectionSize.width / 2 - flowLayout.minimumInteritemSpacing / 2
//            let cellSize = CGSize(width: size, height: size)
//            flowLayout.itemSize = cellSize
//        }
    }

    ...

}

First throw call stack:

0   CoreFoundation                      0x00000001117751e6 __exceptionPreprocess + 294
1   libobjc.A.dylib                     0x000000010d946031 objc_exception_throw + 48
2   CoreFoundation                      0x00000001117b50bc _CFThrowFormattedException + 194
3   CoreFoundation                      0x00000001117a67b1 -[__NSArrayM objectAtIndexedSubscript:] + 177
4   UIKit                               0x000000010ecf2957 -[UICollectionViewFlowLayout _getSizingInfosWithExistingSizingDictionary:] + 1801
5   UIKit                               0x000000010ecf47db -[UICollectionViewFlowLayout _fetchItemsInfoForRect:] + 136
6   UIKit                               0x000000010eced70a -[UICollectionViewFlowLayout collectionViewContentSize] + 66
7   _blackened_                             0x000000010d28812f _T07beSmart22TwoSizesCellFlowLayoutC14collectionViewSC6CGSizeVSo012UICollectionI0C_So0kiG0C6layout10Foundation9IndexPathV13sizeForItemAttF + 79
8   _blackened_                             0x000000010d2882fa _T07beSmart22TwoSizesCellFlowLayoutC14collectionViewSC6CGSizeVSo012UICollectionI0C_So0kiG0C6layout10Foundation9IndexPathV13sizeForItemAttFTo + 122
9   UIKit                               0x000000010ecf2e50 -[UICollectionViewFlowLayout _getSizingInfosWithExistingSizingDictionary:] + 3074
10  UIKit                               0x000000010ecf47db -[UICollectionViewFlowLayout _fetchItemsInfoForRect:] + 136
11  UIKit                               0x000000010eced104 -[UICollectionViewFlowLayout prepareLayout] + 272
12  UIKit                               0x000000010ed1a0a9 -[UICollectionViewData _prepareToLoadData] + 156
13  UIKit                               0x000000010ed1a956 -[UICollectionViewData validateLayoutInRect:] + 53
14  UIKit                               0x000000010ecb0620 -[UICollectionView layoutSubviews] + 260
15  UIKit                               0x000000010e2b17a8 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1515
16  QuartzCore                          0x0000000114d45456 -[CALayer layoutSublayers] + 177
17  QuartzCore                          0x0000000114d49667 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 395
18  QuartzCore                          0x0000000114cd00fb _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 343
19  QuartzCore                          0x0000000114cfd79c _ZN2CA11Transaction6commitEv + 568
20  QuartzCore                          0x0000000114cfe51a _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 76
21  CoreFoundation                      0x0000000111717607 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
22  CoreFoundation                      0x000000011171755e __CFRunLoopDoObservers + 430
23  CoreFoundation                      0x00000001116fbb81 __CFRunLoopRun + 1537
24  CoreFoundation                      0x00000001116fb30b CFRunLoopRunSpecific + 635
25  GraphicsServices                    0x0000000113ecea73 GSEventRunModal + 62
26  UIKit                               0x000000010e1e2057 UIApplicationMain + 159
27  _blackened_                             0x000000010d2896c7 main + 55
28  libdyld.dylib                       0x00000001127b7955 start + 1

Regards,

Aaron


Solution

  • Problem solved.

    I think the problem is that collectionView(_:layout:sizeForItemAt:) is called to determine the collectionViewContentSize property. But I tried to access collectionViewContentSize inside it.

    I fixed it using the bounds of collectionView.

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let viewSize = collectionView.bounds.size
        var cellWidth = viewSize.width / 2
    
        if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout {
            let usableViewWidth = viewSize.width - flowLayout.sectionInset.left - flowLayout.sectionInset.right
            cellWidth = usableViewWidth / 2 - flowLayout.minimumInteritemSpacing / 2
        }
        return CGSize(width: cellWidth, height: cellWidth)
    }