@@ -80,36 +80,48 @@ - (void)_updateVisibleNodeIndexPaths
8080 [_layoutController setVisibleNodeIndexPaths: visibleNodePaths];
8181 }
8282
83- ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController: self ];
83+ NSArray *allNodes = [_dataSource completedNodes ];
84+ NSArray *currentSectionNodes = nil ;
85+ NSInteger currentSectionIndex = -1 ; // Will be unequal to any indexPath.section, so we set currentSectionNodes.
8486
85- NSSet *visibleIndexPaths = [_layoutController indexPathsForScrolling: _scrollDirection rangeType: ASLayoutRangeTypeVisible];
87+ NSUInteger numberOfSections = [allNodes count ];
88+ NSUInteger numberOfNodesInSection = 0 ;
8689
87- #if RangeControllerLoggingEnabled
88- NSMutableArray *modified = [NSMutableArray array ];
89- #endif
90+ NSSet *visibleIndexPaths = [NSSet setWithArray: visibleNodePaths];
91+ // = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible];
92+ NSSet *displayIndexPaths = nil ;
93+ NSSet *fetchDataIndexPaths = nil ;
94+ NSMutableSet *allIndexPaths = nil ;
95+ NSMutableArray *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array ] : nil );
96+
97+ ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController: self ];
9098
9199 if (ASInterfaceStateIncludesVisible (selfInterfaceState)) {
92100 // If we are already visible, get busy! Better get started on preloading before the user scrolls more...
93- NSSet * fetchDataIndexPaths = [_layoutController indexPathsForScrolling: _scrollDirection rangeType: ASLayoutRangeTypeFetchData];
94- NSSet * displayIndexPaths = [_layoutController indexPathsForScrolling: _scrollDirection rangeType: ASLayoutRangeTypeDisplay];
101+ fetchDataIndexPaths = [_layoutController indexPathsForScrolling: _scrollDirection rangeType: ASLayoutRangeTypeFetchData];
102+ displayIndexPaths = [_layoutController indexPathsForScrolling: _scrollDirection rangeType: ASLayoutRangeTypeDisplay];
95103
96104 // Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint.
97- NSMutableSet * allIndexPaths = [fetchDataIndexPaths mutableCopy ];
105+ allIndexPaths = [fetchDataIndexPaths mutableCopy ];
98106 [allIndexPaths unionSet: displayIndexPaths];
99107 [allIndexPaths unionSet: visibleIndexPaths];
108+ } else {
109+ allIndexPaths = [visibleIndexPaths mutableCopy ];
110+ }
111+
112+ // Sets are magical. Add anything we had applied interfaceState to in the last update, so we can clear any
113+ // range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic
114+ // scroll or major main thread stall could cause entirely disjoint sets, but we must visit all.
115+ NSSet *allCurrentIndexPaths = [allIndexPaths copy ];
116+ [allIndexPaths unionSet: _allPreviousIndexPaths];
117+ _allPreviousIndexPaths = allCurrentIndexPaths;
118+
119+ for (NSIndexPath *indexPath in allIndexPaths) {
120+ // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it.
121+ // For consistency, make sure each node knows that it should measure itself if something changes.
122+ ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout;
100123
101- // Sets are magical. Add anything we had applied interfaceState to in the last update, so we can clear any
102- // range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic
103- // scroll or major main thread stall could cause entirely disjoint sets, but we must visit all.
104- NSSet *allCurrentIndexPaths = [allIndexPaths copy ];
105- [allIndexPaths unionSet: _allPreviousIndexPaths];
106- _allPreviousIndexPaths = allCurrentIndexPaths;
107-
108- for (NSIndexPath *indexPath in allIndexPaths) {
109- // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it.
110- // For consistency, make sure each node knows that it should measure itself if something changes.
111- ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout;
112-
124+ if (ASInterfaceStateIncludesVisible (selfInterfaceState)) {
113125 if ([fetchDataIndexPaths containsObject: indexPath]) {
114126 interfaceState |= ASInterfaceStateFetchData;
115127 }
@@ -119,39 +131,49 @@ - (void)_updateVisibleNodeIndexPaths
119131 if ([visibleIndexPaths containsObject: indexPath]) {
120132 interfaceState |= ASInterfaceStateVisible;
121133 }
122-
123- ASDisplayNode *node = [_dataSource rangeController: self nodeAtIndexPath: indexPath];
124- ASDisplayNodeAssert (node.hierarchyState & ASHierarchyStateRangeManaged, @" All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset." );
125- // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState.
126- if (node.interfaceState != interfaceState) {
127- #if RangeControllerLoggingEnabled
128- [modified addObject: indexPath];
129- #endif
130- [node recursivelySetInterfaceState: interfaceState];
134+ } else {
135+ // If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the
136+ // instant we come onscreen. So, fetch data and display all of those things, but don't waste resources preloading yet.
137+ // We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:.
138+
139+ // Set Layout, Fetch Data, Display. DO NOT set Visible: even though these elements are in the visible range / "viewport",
140+ // our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above.
141+ if ([allCurrentIndexPaths containsObject: indexPath]) {
142+ // We might be looking at an indexPath that was previously in-range, but now we need to clear it.
143+ // In that case we'll just set it back to MeasureLayout. Only set Display | FetchData if in allCurrentIndexPaths.
144+ interfaceState |= ASInterfaceStateDisplay;
145+ interfaceState |= ASInterfaceStateFetchData;
131146 }
132147 }
133- } else {
134- // If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the
135- // instant we come onscreen. So, fetch data and display all of those things, but don't waste resources preloading yet.
136- // We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:.
137148
138- for (NSIndexPath *indexPath in visibleIndexPaths) {
139- // Set Layout, Fetch Data, Display. DO NOT set Visible: even though these elements are in the visible range / "viewport",
140- // our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above.
141- ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout | ASInterfaceStateFetchData | ASInterfaceStateDisplay;
149+ NSInteger section = indexPath.section ;
150+ NSInteger row = indexPath.row ;
151+
152+ if (section >= 0 && row >= 0 && section < numberOfSections) {
153+ if (section != currentSectionIndex) {
154+ // Often we'll be dealing with indexPaths in the same section, but the set isn't sorted and we may even bounce
155+ // between the same ones. Still, this saves dozens of method calls to access the inner array and count.
156+ currentSectionNodes = [allNodes objectAtIndex: section];
157+ numberOfNodesInSection = [currentSectionNodes count ];
158+ currentSectionIndex = section;
159+ }
142160
143- ASDisplayNode *node = [_dataSource rangeController: self nodeAtIndexPath: indexPath];
144- ASDisplayNodeAssert (node.hierarchyState & ASHierarchyStateRangeManaged, @" All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset." );
145- // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState.
146- if (node.interfaceState != interfaceState) {
147- #if RangeControllerLoggingEnabled
148- [modified addObject: indexPath];
149- #endif
150- [node recursivelySetInterfaceState: interfaceState];
161+ if (row < numberOfNodesInSection) {
162+ ASDisplayNode *node = [currentSectionNodes objectAtIndex: row];
163+
164+ ASDisplayNodeAssert (node.hierarchyState & ASHierarchyStateRangeManaged, @" All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset." );
165+ // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState.
166+ if (node.interfaceState != interfaceState) {
167+ [modifiedIndexPaths addObject: indexPath];
168+ [node recursivelySetInterfaceState: interfaceState];
169+ }
151170 }
152171 }
153172 }
154173
174+ _rangeIsValid = YES ;
175+ _queuedRangeUpdate = NO ;
176+
155177#if RangeControllerLoggingEnabled
156178 NSSet *visibleNodePathsSet = [NSSet setWithArray: visibleNodePaths];
157179 BOOL setsAreEqual = [visibleIndexPaths isEqualToSet: visibleNodePathsSet];
@@ -161,9 +183,9 @@ - (void)_updateVisibleNodeIndexPaths
161183 NSLog (@" custom: %@ " , visibleNodePathsSet);
162184 }
163185
164- [modified sortUsingSelector: @selector (compare: )];
186+ [modifiedIndexPaths sortUsingSelector: @selector (compare: )];
165187
166- for (NSIndexPath *indexPath in modified ) {
188+ for (NSIndexPath *indexPath in modifiedIndexPaths ) {
167189 ASDisplayNode *node = [_dataSource rangeController: self nodeAtIndexPath: indexPath];
168190 ASInterfaceState interfaceState = node.interfaceState ;
169191 BOOL inVisible = ASInterfaceStateIncludesVisible (interfaceState);
@@ -172,9 +194,6 @@ - (void)_updateVisibleNodeIndexPaths
172194 NSLog (@" indexPath %@ , Visible: %d , Display: %d , FetchData: %d " , indexPath, inVisible, inDisplay, inFetchData);
173195 }
174196#endif
175-
176- _rangeIsValid = YES ;
177- _queuedRangeUpdate = NO ;
178197}
179198
180199#pragma mark - Cell node view handling
0 commit comments