diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 099947457c..5990908ce6 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -867,7 +867,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController { ASDisplayNodeAssertMainThread(); - return [self indexPathsForVisibleItems]; + // Calling visibleNodeIndexPathsForRangeController: will trigger UIKit to call reloadData if it never has, which can result + // in incorrect layout if performed at zero size. We can use the fact that nothing can be visible at zero size to return fast. + BOOL isZeroSized = CGRectEqualToRect(self.bounds, CGRectZero); + return isZeroSized ? @[] : [self indexPathsForVisibleItems]; } - (CGSize)viewportSizeForRangeController:(ASRangeController *)rangeController diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 1cad0832db..f7f57a26fb 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -718,6 +718,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; { ASDisplayNodeAssertMainThread(); + // Calling indexPathsForVisibleRows will trigger UIKit to call reloadData if it never has, which can result + // in incorrect layout if performed at zero size. We can use the fact that nothing can be visible at zero size to return fast. + if (CGRectEqualToRect(self.bounds, CGRectZero)) { + return @[]; + } + NSArray *visibleIndexPaths = self.indexPathsForVisibleRows; if (_pendingVisibleIndexPath) { diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 1b0e828814..e84e0e96c6 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -149,6 +149,17 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; return; } + // allNodes is a 2D array: it contains arrays for each section, each containing nodes. + NSArray *allNodes = [_dataSource completedNodes]; + NSUInteger numberOfSections = [allNodes count]; + + if (_allPreviousIndexPaths.count == 0 && allNodes.count == 0) { + // In certain cases, such as on app suspend, an update may be triggered before we've loaded anything. + // For example, an ASCollectionNode inside another scrollable area will not load content until it has entered + // the display range, but the object may have been allocated by a cell and added to the set of active range controllers. + return; + } + // TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges // Example: ... = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible]; NSArray *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self]; @@ -165,10 +176,6 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; [_layoutController setVisibleNodeIndexPaths:visibleNodePaths]; } - // allNodes is a 2D array: it contains arrays for each section, each containing nodes. - NSArray *allNodes = [_dataSource completedNodes]; - NSUInteger numberOfSections = [allNodes count]; - NSArray *currentSectionNodes = nil; NSInteger currentSectionIndex = -1; // Set to -1 so we don't match any indexPath.section on the first iteration. NSUInteger numberOfNodesInSection = 0;