Crash in iOS18

I have a UITableView which contains a UICollectionView in the first row. It used to work fine in iOS17, but now I get a crash when running with Xcode 16 / iOS18 beta:

Expected dequeued view to be returned to the collection view in preparation for display. When the collection view's data source is asked to provide a view for a given index path, ensure that a single view is dequeued and returned to the collection view. Avoid dequeuing views without a request from the collection view. For retrieving an existing view in the collection view, use -[UICollectionView cellForItemAtIndexPath:] or -[UICollectionView supplementaryViewForElementKind:atIndexPath:]

This is my UITableView delegate call:

    AddEditDataCell *cell = nil;
    if (indexPath.section == 0) {
        
        if (indexPath.row == 0) {
            AddEditDataContactsCell *contactNameCell = (AddEditDataContactsCell *)[self cellForContactNamesCollectionAtIndexPath:indexPath tableView:tableView];
            return contactNameCell; 


- (AddEditDataContactsCell *)cellForContactNamesCollectionAtIndexPath:(NSIndexPath *)indexPath tableView:(UITableView *)tableView {
    AddEditDataContactsCell *contactsCell = (AddEditDataContactsCell *)[self.tableView dequeueReusableCellWithIdentifier:@"ContactsCell" forIndexPath:indexPath];
    if (self.collectionNameCell == nil) {
        self.collectionNameCell = [contactsCell.collectionView dequeueReusableCellWithReuseIdentifier:@"LogContactNameCollectionCellIdentifier" forIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
        contactsCell.nameCellDelegate = self;
    }

    contactsCell.frame = CGRectZero;
    [contactsCell setNeedsLayout];
    [contactsCell.collectionView reloadData];
    
    contactsCell.collectionViewHeightConstraint.constant = contactsCell.collectionView.collectionViewLayout.collectionViewContentSize.height;
    
    [contactsCell.collectionView.collectionViewLayout invalidateLayout];
    return contactsCell;
}

I'm getting a crash for decoration views somewhere inside UIKitCore which is definitely out of my control – the code doesn't dequeue or care about instantiation of decoration views at all – all it does is inserting the registered layout attributes in the flow layout subclass, the rest of it (instantiation of the registered view & queuing/dequeuing) is provided by UIKit.

I have a ton of crashes starting with iOS 18, not a single one on iOS 17 and prior. Call stack:

Thread 1: "*** -[__NSArrayM objectAtIndex:]: index 4 beyond bounds [0 .. 3]"

#1	0x000000018039c67c in -[__NSArrayM objectAtIndex:] ()
#2	0x0000000185a9aa54 in -[_UICollectionViewSubviewManager dequeueReusableViewWithReuseIdentifier:elementKind:elementCategory:] ()
#3	0x00000001851bc3cc in -[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:] ()
#4	0x00000001851a6ab4 in -[UICollectionView _createPreparedSupplementaryViewForElementOfKind:atIndexPath:layout:withLayoutAttributes:applyAttributes:] ()
#5	0x00000001851af69c in -[UICollectionView _createVisibleViewsForSingleCategoryAttributes:limitCreation:fadeForBoundsChange:] ()
#6	0x00000001851af824 in -[UICollectionView _createVisibleViewsForAttributes:fadeForBoundsChange:notifyLayoutForVisibleCellsPass:] ()
#7	0x00000001851ad9c4 in -[UICollectionView _updateVisibleCellsNow:] ()

Array index out of bounds. Definitely not an array in reach, very internal operations. This is also preceded by the following warning thrown into Console:

UICollectionView internal inconsistency: attempted to queue view that is already in the reuse queue. Collection view: <TripItineraryDayCollectionView: 0x146917400; baseClass = UICollectionView; frame = (0 0; 402 682); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x600000e5d770>; backgroundColor = UIExtendedGrayColorSpace 0 0; layer = <CALayer: 0x600000393440>; contentOffset: {0, 0}; contentSize: {402, 662}; adjustedContentInset: {0, 0, 0, 0}; layout: <TripItineraryDayViewLayout: 0x1463d6770>; dataSource: <TripItineraryDayView: 0x1463d6360; frame = (0 102; 402 682); clipsToBounds = YES; autoresize = W+H; backgroundColor = UIExtendedGrayColorSpace 0 0; layer = <CALayer: 0x6000003974a0>>>; view: <TransportLineDecorationView: 0x1059e51a0; baseClass = UICollectionReusableView; frame = (14 257; 54 69); userInteractionEnabled = NO; layer = <CALayer: 0x6000004180c0>>; layout attributes: <TransportDecorationViewLayoutAttributes: 0x1463e34a0; index path: (2-4); element kind: (TripItineraryDayTransportDecorationView); frame = (14 257; 54 69)>

Disabling the decoration views makes the crash go away.

Something has probably changed in iOS 18 which might have sense for cells (where providing the views in the delegate properly is mandatory) or supplementary views, yet it doesn't work really well with decoration views. It gives me the impression that supplementary and decoration views have been merged into one queue stack internally, but something's not right.

I had similar issue with iOS18.(issue exists in below code)

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCollectionViewCell.collectionTextCell.rawValue, for: indexPath) as? CollectionTextCell else {

        return UICollectionViewCell()

    }

    cell.isDetailPage = isFromDetailPage

    cell.manageFocus(isFocusSelected: isSelectedFocus == indexPath.row)

    switch detailComponentType[indexPath.row] {

    case .episodes, .trailer, .morelikethis, .castcrew, .OtherChannelsLive, .recommendeLivechannelsforyou, .similarContentLive, .highlightsSports, .otherTournaments, .readMore, .playlist:

        cell.manageData(data:  detailComponentType[indexPath.row])

    case .bestOfProvider: cell.textlbl.text = Constant.bestOfProvider + /content_Data?.where_to_watch?.first?.provider?.name

    case .otherSportsType: cell.textlbl.text = Constant.otherSportsType + /content_Data?.format + " " + Constant.tournaments

    }

    return cell

}

I am getting the same crash but my use case shouldn't be triggering the crash by Apple's description.

In my case, I have a custom function to dynamically calculate cell sizes defined as:

public func intrinsicCellSize(
        forCellAtIndexPath indexPath: IndexPath,
        width: CGFloat? = nil,
        height: CGFloat? = nil
    ) -> CGSize {
        guard let dataSource = dataSource else {
            return .zero
        }
        let cell = dataSource.collectionView(self, cellForItemAt: indexPath)
        
        if let cell = cell as? HeightProvidingCell, let width {
            return .init(width: width, height: cell.provideHeight(for: width))
        }

        let targetSize = CGSize(
            width: width ?? UIView.layoutFittingExpandedSize.width,
            height: height ?? UIView.layoutFittingExpandedSize.height
        )
        let size = cell.contentView.systemLayoutSizeFitting(
            targetSize,
            withHorizontalFittingPriority: .fittingSizeLevel,
            verticalFittingPriority: .fittingSizeLevel
        )
        return size
    }

I call this in the sizeForItemAt delegate function. This was working flawlessly before updating macOS and Xcode but now this crashes. Specifically, it crashes with the call

let cell = dataSource.collectionView(self, cellForItemAt: indexPath)

By Apple's error message, this should be fine as I'm not calling deque, the error message even suggests that you use this method.

Perhaps the issue is the order in which UIKit calls the delegate and dataSource methods. By adding print statements, I discovered that sizeForItemAt is called before cellForItemAt so in my case, the flow of function calls is:

  • sizeForItemAt (Called by UIKit)
  • cellForItemAt (Called by me)
  • cellForItemAt (Called by UIKit)

Perhaps UIKit is expecting the first call of cellForItemAt to be made by UIKit and not the user. If this is the case, my app is broken since I relied heavily on this

I just want to provide a solution that I've found for our use case. We had the following:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: someCellId,
            for: indexPath
        ) as! SomeCollectionViewCell

        // some logic
        return cell

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: someCellId,
            for: indexPath
        ) as! SomeCollectionViewCell

        // some more logic
}

I have changed the above code to the below, which fixed the crash.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: someCellId,
            for: indexPath
        ) as! SomeCollectionViewCell

        // some logic
        return cell

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let cell = collectionView.cellForItem(at: indexPath) as? SomeCollectionViewCell

        // some more logic
}

As you can see, we were using the dequeueReusableCell function twice, which caused the crash. Using it only once fixed it for us.

I also encountered the crash problem. This is the original code:

  • (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { HomeCellUnconnected *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellClassNameUnconnected forIndexPath:indexPath];

    if (self.datas.count > indexPath.row) { NSString *cellClassName = self.datas[indexPath.row]; cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellClassName forIndexPath:indexPath]; }

    return cell;

}

this is the modified code:

  • (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { NSString *cellClassName = self.datas[indexPath.row]; UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellClassName forIndexPath:indexPath];

    return cell;

}

There is a site that analyzes and resolves the issue; please refer to it.

https://rldd.tistory.com/696

For anyone using IGListKit:

The issue is exactly as stated: a cell or supplementaryView is being dequeued more than once which results in a crash on iOS 18.

I found the issue in one of our ListSectionController objects.

final class MySectionController: ListSectionController {
  // ...
  override func cellForItem(at index: Int) -> UICollectionViewCell {
    guard let cell = collectionContext?.dequeueReusableCell(of: MyCell.self, for: self, at: index) as? MyCell else {
      return UICollectionViewCell()
    }
    // ...
    return cell
  }
}

 // ... elsewhere:
 let sourceView = cellForItem(at: 0)

That last line mistakenly calls cellForItem on the section controller instead of the collectionContext.

To fix:

let sourceView = collectionContext?.cellForItem(at: 0, sectionController: self)

Hi all, I have this problem - Expected dequeued view to be returned to the collection view in preparation for display. When the collection view's data source is asked to provide a view for a given index path, ensure that a single view is dequeued and returned to the collection view. Avoid dequeuing views without a request from the collection view. For retrieving an existing view in the collection view, use -[UICollectionView cellForItemAtIndexPath:] or -[UICollectionView supplementaryViewForElementKind:atIndexPath:].

My code crashed just IOS18+

My code -

 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
      guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SectionModsCell.cellIndetifire, for: indexPath) as? SectionModsCell else { return UICollectionViewCell() }
        
        switch mainState {
        case .main:
            
            if ApphudManager.sharedInstance.isPremium {
                let cellData = sectionModsData[indexPath.item]
                return cell.setupCell(data: cellData)
            } else {
                let newIndex = getIndex(for: indexPath)
                
                
                guard indexPath.item != adIndexPath || adIndexPath == 0 else {
                    if UIDevice.current.userInterfaceIdiom == .pad {
                        let bannerCell = collectionView.dequeueReusableCell(withReuseIdentifier: IpadNativeCell.cellIndetifire, for: indexPath) as! IpadNativeCell
                        bannerCell.setupCell(nativeAd: nativeAd)
                        return bannerCell
                    } else {
                        let bannerCell = collectionView.dequeueReusableCell(withReuseIdentifier: SmallNativeCell.cellIndetifire, for: indexPath) as! SmallNativeCell
                        bannerCell.setupCell(nativeAd: nativeAd)
                        return bannerCell
                    }
                }
                
                let cellData = sectionModsData[newIndex]
                return cell.setupCell(data: cellData)
            }
            
        case .search:
            let cellData = filteredModsData[indexPath.item]
            return cell.setupCell(data: cellData)
        }

    }

And this code not give me error, my mind is **** (

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
        
        switch mainState {
        case .main:
            
            if ApphudManager.sharedInstance.isPremium {
                
                guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SectionModsCell.cellIndetifire, for: indexPath) as? SectionModsCell else { return UICollectionViewCell() }
                
                let cellData = sectionModsData[indexPath.item]
                return cell.setupCell(data: cellData)
            } else {
                let newIndex = getIndex(for: indexPath)
                
                
                guard indexPath.item != adIndexPath || adIndexPath == 0 else {
                    if UIDevice.current.userInterfaceIdiom == .pad {
                        let bannerCell = collectionView.dequeueReusableCell(withReuseIdentifier: IpadNativeCell.cellIndetifire, for: indexPath) as! IpadNativeCell
                        bannerCell.setupCell(nativeAd: nativeAd)
                        return bannerCell
                    } else {
                        let bannerCell = collectionView.dequeueReusableCell(withReuseIdentifier: SmallNativeCell.cellIndetifire, for: indexPath) as! SmallNativeCell
                        bannerCell.setupCell(nativeAd: nativeAd)
                        return bannerCell
                    }
                }
                
                guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SectionModsCell.cellIndetifire, for: indexPath) as? SectionModsCell else { return UICollectionViewCell() }
                
                let cellData = sectionModsData[newIndex]
                return cell.setupCell(data: cellData)
            }
            
        case .search:
            
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SectionModsCell.cellIndetifire, for: indexPath) as? SectionModsCell else { return UICollectionViewCell() }
            
            let cellData = filteredModsData[indexPath.item]
            return cell.setupCell(data: cellData)
        }

    }

My code

extension MyViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    
    func collectionView(
        _ collectionView: UICollectionView,
        cellForItemAt indexPath: IndexPath
    ) -> UICollectionViewCell {
        if let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: "CollectionViewCellID",
            for: indexPath
        ) as? CollectionViewCell {
            cell.setup()
            return cell
        }
        return UICollectionViewCell()
    }
    
    func collectionView(
        _ collectionView: UICollectionView,
        didSelectItemAt indexPath: IndexPath
    ) {
        // Unnecessary dequeue
        guard collectionView.dequeueReusableCell(
            withReuseIdentifier: "CollectionViewCellID",
            for: indexPath
        ) is CollectionViewCell else { return }
        // My action for selecting cell
        print("Cell Selected")
    }
}

Error:


*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Expected dequeued view to be returned to the collection view in preparation for display. When the collection view's data source is asked to provide a view for a given index path, ensure that a single view is dequeued and returned to the collection view. Avoid dequeuing views without a request from the collection view. For retrieving an existing view in the collection view, use -[UICollectionView cellForItemAtIndexPath:] or -[UICollectionView supplementaryViewForElementKind:atIndexPath:].

Solution:

The problem was doing unnecessary dequeuing in didSelectItemAt when selecting the cell. In previous iOS like 17 or 16 or lower, it was allowed to dequeue where it is not really needed but from iOS 18, it may restricted to unnecessary dequeuing. So better to remove dequeue and use cellForItem(at) if we need to get cell from collection view.

Example

extension MyViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    
    func collectionView(
        _ collectionView: UICollectionView,
        cellForItemAt indexPath: IndexPath
    ) -> UICollectionViewCell {
        if let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: "CollectionViewCellID",
            for: indexPath
        ) as? CollectionViewCell {
            cell.setup()
            return cell
        }
        return UICollectionViewCell()
    }
    
    func collectionView(
        _ collectionView: UICollectionView,
        didSelectItemAt indexPath: IndexPath
    ) {
        guard collectionView.cellForItem(at: indexPath) is CollectionViewCell else { return }
        // My action for selecting cell
        print("Cell Selected")
    }
}

I Found out some situation will be crash in iOS 18.

When u want to get cell or header(footer) by calling dequeueCell or dequeueSupplementaryView. And which isn't in collectionView dataSource function like, 'cellForRow' or viewForSupplementaryElementOfKind, It will be crash!!!

So, if u have a function call getFootView() like below:

    func getFootView(indexPath: IndexPath) -> RangePriceFootView? {
        return collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "FOOTER", for: indexPath)
    }

Call this function at viewForSupplementaryElementOfKind, it is legal.

   func collectionView(
        _ collectionView: UICollectionView,
        viewForSupplementaryElementOfKind kind: String,
        at indexPath: IndexPath
    ) -> UICollectionReusableView {
        let footer = getFootView(indexPath: indexPath)
        // footer do something
        return footer
    }

but if u call this from other place than viewForSupplementaryElementOfKind, it will be crash

   func updateUI() {
        let footer = getFootView(indexPath: indexPath)
        // do something
    }

This warning below tell us, call use -[UlCollectionView cellFortemAtindexPath:] or -[UlCollectionView supplementaryViewForElementKind:atIndexPath:] when u are not in config function.

Thread 1: "Expected dequeued view to be returned to the collection view in preparation for display. When the collection view's data source is asked to provide a view for a given index path, ensure that a single view is dequeued and returned to the collection view. Avoid dequeuing views without a request from the collection view. For retrieving an existing view in the collection view, use -[UlCollectionView cellForltemAtindexPath:] or -[UlCollectionView supplementaryViewForElementKind:atindexPath:].

I want to share that I had exactly the same crash description, but my issue was related to NSFetchedResultsControllerDelegate. After refactoring, issue disappeared. I hope it may help some of us who trying to find the root case.

Best way to debug is to enable Exception Breakpoint.

Crash was called in this function inside the batch update. Worked fine on Xcode 15, but crashes on Xcode 16.

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        collectionView?.performBatchUpdates({

          //crash was here

        }, completion: { [weak self] finished in
            //completion
        })
    }
}

Crash in iOS18
 
 
Q