Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix UICollectionView cell enumeration hitting asserts in Xcode 16 #1307

Merged

Conversation

justinseanmartin
Copy link
Contributor

In Xcode 16, an assertion was to UICollectionView:

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:].

Rather than calling -[UICollectionViewDataSource collectionView:cellForItemAtIndexPath:] and [UICollectionViewDelegate collectionView:willDisplayCell:forItemAtIndexPath:], instead we will scroll the collection view's content offset to make the offscreen cells be realized by UIKit. If the cell matches the predicate, then we will return the matching element (which should now be on screen). If a match isn't found in any of the cells, scroll back to the original scroll position and keep searching the view hierarchy for a matching element.

One notable thing to call out is that unlike cell enumeration in a UITableView using cellForRowAtIndexPath:, the UICollectionView equivalent cellForItemAtIndexPath: will return a nil cell unless a layour pass is performed. We could either spin the runloop and let UIKit update on its own or directly call layoutIfNeeded on the collection view. We're going with the latter, because it is faster on Xcode 16 and avoids a bunch of unnecessary and distracting redrawing of the screen at each scroll position.

A new API [viewTester usingCurrentFrame] was introduced in #1296 that can avoid this logic of implicitly scrolling collection views where that is needed or desired.

Fixes #1303

CC: @jdev7 @danielbarela @ispatel22 - Could you give this change a test locally and see if this addresses the issues you're seeing and see if it surfaces any other problems? I've landed a couple other changes recently, so it is possible that those could also have altered behavior in minor ways (#1296, #1300 and #1301).

Enumerate the cells by scrolling the collection view's content offset
and calling `layoutIfNeeded` instead of programmatically enumerating
them using the data source and delegate APIs directly.
@danielbarela
Copy link
Contributor

I tested your fix, and it appears to fix my problem related to the collection view. Thank you.

CGRect visibleRect = [collectionView layoutAttributesForItemAtIndexPath:indexPath].frame;
[collectionView scrollRectToVisible:visibleRect animated:NO];
[collectionView layoutIfNeeded];
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know the KIF internal best practices, but is there some warning or error we should throw if this returns a nil cell? Or meh?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could NSAssert that the cell is non-nil to abort the search. I'll add that and see how it goes. Seems to pass locally at least.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I don't know if an assert is worth it – maybe a log or something? Unclear on the risk of false positives vs. false negatives where the test would just fail.


// Skip this cell if it isn't the one we're looking for
// Sometimes we get cells with no size here which can cause an endless loop, so we ignore those
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why this would cause an endless loop, but I'm guessing that realizing these cells in a more realistic way may sidestep this issue. We can always add this back later if need be.

return [self accessibilityElementMatchingBlock:matchBlock disableScroll:NO];
}
}

[collectionView setContentOffset:initialPosition.origin];
Copy link
Contributor

@kyleve kyleve Sep 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do a layoutIfNeeded after scrollRectToVisible above; should we do that here too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, doesn't really hurt. I'll add it for consistency.

@justinseanmartin justinseanmartin merged commit 2b1164c into master Sep 24, 2024
5 checks passed
@justinseanmartin justinseanmartin deleted the jmartin/collection-view-cell-enumeration-assert branch September 24, 2024 23:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Broken tests with Xcode 16-RC / iOS 18
5 participants