Skip to content

Commit

Permalink
Fix UICollectionView cell enumeration hitting asserts in Xcode 16
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Justin Martin committed Sep 24, 2024
1 parent 8f094a6 commit a3a3fb8
Showing 1 changed file with 19 additions and 28 deletions.
47 changes: 19 additions & 28 deletions Sources/KIF/Additions/UIView-KIFAdditions.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
#import "KIFUITestActor.h"
#import <WebKit/WebKit.h>

#define DRAG_TOUCH_DELAY 0.01
#define CELL_SCROLL_DELAY_STABILIZATION 0.05

double KIFDegreesToRadians(double deg) {
return (deg) / 180.0 * M_PI;
}
Expand Down Expand Up @@ -310,7 +313,6 @@ - (UIAccessibilityElement *)accessibilityElementMatchingBlock:(BOOL(^)(UIAccessi
}
}];

CFTimeInterval delay = 0.05;
for (NSUInteger section = 0, numberOfSections = [tableView numberOfSections]; section < numberOfSections; section++) {
for (NSUInteger row = 0, numberOfRows = [tableView numberOfRowsInSection:section]; row < numberOfRows; row++) {
if (!self.window) {
Expand All @@ -328,7 +330,7 @@ - (UIAccessibilityElement *)accessibilityElementMatchingBlock:(BOOL(^)(UIAccessi
}

@autoreleasepool {
// Scroll to the cell and wait for the animation to complete. Using animations here may not be optimal.
// Scroll to the cell and wait for the animation to complete.
CGRect sectionRect = [tableView rectForRowAtIndexPath:indexPath];
[tableView scrollRectToVisible:sectionRect animated:NO];

Expand All @@ -342,24 +344,25 @@ - (UIAccessibilityElement *)accessibilityElementMatchingBlock:(BOOL(^)(UIAccessi
}

// Note: using KIFRunLoopRunInModeRelativeToAnimationSpeed here may cause tests to stall
CFRunLoopRunInMode(UIApplicationCurrentRunMode, delay, false);
CFRunLoopRunInMode(UIApplicationCurrentRunMode, CELL_SCROLL_DELAY_STABILIZATION, false);
return [self accessibilityElementMatchingBlock:matchBlock disableScroll:NO];
}
}

//if we're in a picker (scrollView), let's make sure we set the position back to how it was last set.
if(scrollView != nil && scrollContentOffset.x != -1.0)
if (scrollView != nil && scrollContentOffset.x != -1.0)
{
[scrollView setContentOffset:scrollContentOffset];
} else {
[tableView setContentOffset:initialPosition.origin];
}
CFRunLoopRunInMode(UIApplicationCurrentRunMode, delay, false);

CFRunLoopRunInMode(UIApplicationCurrentRunMode, CELL_SCROLL_DELAY_STABILIZATION, false);
} else if ([self isKindOfClass:[UICollectionView class]]) {
UICollectionView *collectionView = (UICollectionView *)self;

CGRect initialPosition = CGRectMake(collectionView.contentOffset.x, collectionView.contentOffset.y, collectionView.frame.size.width, collectionView.frame.size.height);
NSArray *indexPathsForVisibleItems = [collectionView indexPathsForVisibleItems];

for (NSUInteger section = 0, numberOfSections = [collectionView numberOfSections]; section < numberOfSections; section++) {
for (NSUInteger item = 0, numberOfItems = [collectionView numberOfItemsInSection:section]; item < numberOfItems; item++) {
if (!self.window) {
Expand All @@ -373,36 +376,26 @@ - (UIAccessibilityElement *)accessibilityElementMatchingBlock:(BOOL(^)(UIAccessi
}

@autoreleasepool {
// Get the cell directly from the dataSource because UICollectionView will only vend visible cells
UICollectionViewCell *cell = [collectionView.dataSource collectionView:collectionView cellForItemAtIndexPath:indexPath];

// The cell contents might change just prior to being displayed, so we simulate the cell appearing onscreen
if ([collectionView.delegate respondsToSelector:@selector(collectionView:willDisplayCell:forItemAtIndexPath:)]) {
[collectionView.delegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath];
}

CGRect visibleRect = [collectionView layoutAttributesForItemAtIndexPath:indexPath].frame;
[collectionView scrollRectToVisible:visibleRect animated:NO];
[collectionView layoutIfNeeded];
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
UIAccessibilityElement *element = [cell accessibilityElementMatchingBlock:matchBlock notHidden:NO disableScroll:NO];

// Remove the cell from the collection view so that it doesn't stick around
[cell removeFromSuperview];

// 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
if (!element || CGSizeEqualToSize(cell.frame.size, CGSizeZero)) {
if (!element) {
continue;
}
}

// Scroll to the cell and wait for the animation to complete
CGRect frame = [collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath].frame;
[collectionView scrollRectToVisible:frame animated:YES];
// Note: using KIFRunLoopRunInModeRelativeToAnimationSpeed here may cause tests to stall
CFRunLoopRunInMode(UIApplicationCurrentRunMode, 0.5, false);

// Now try finding the element again
CFRunLoopRunInMode(UIApplicationCurrentRunMode, CELL_SCROLL_DELAY_STABILIZATION, false);
return [self accessibilityElementMatchingBlock:matchBlock disableScroll:NO];
}
}

[collectionView setContentOffset:initialPosition.origin];
CFRunLoopRunInMode(UIApplicationCurrentRunMode, CELL_SCROLL_DELAY_STABILIZATION, false);
}
}

Expand Down Expand Up @@ -592,8 +585,6 @@ - (void)twoFingerTapAtPoint:(CGPoint)point {
[[UIApplication sharedApplication] kif_sendEvent:event];
}

#define DRAG_TOUCH_DELAY 0.01

- (void)longPressAtPoint:(CGPoint)point duration:(NSTimeInterval)duration
{
UITouch *touch = [[UITouch alloc] initAtPoint:point inView:self];
Expand Down

0 comments on commit a3a3fb8

Please sign in to comment.