From a3a3fb8ba991e8a7e1b78dae00f6c09f36a8be09 Mon Sep 17 00:00:00 2001 From: Justin Martin Date: Tue, 24 Sep 2024 11:02:07 -0700 Subject: [PATCH] Fix UICollectionView cell enumeration hitting asserts in Xcode 16 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. --- Sources/KIF/Additions/UIView-KIFAdditions.m | 47 +++++++++------------ 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/Sources/KIF/Additions/UIView-KIFAdditions.m b/Sources/KIF/Additions/UIView-KIFAdditions.m index 97e9c707..bde10305 100644 --- a/Sources/KIF/Additions/UIView-KIFAdditions.m +++ b/Sources/KIF/Additions/UIView-KIFAdditions.m @@ -17,6 +17,9 @@ #import "KIFUITestActor.h" #import +#define DRAG_TOUCH_DELAY 0.01 +#define CELL_SCROLL_DELAY_STABILIZATION 0.05 + double KIFDegreesToRadians(double deg) { return (deg) / 180.0 * M_PI; } @@ -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) { @@ -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]; @@ -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) { @@ -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); } } @@ -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];