From 929d118c962dcf595386daf5c91ce30e1f8e577d Mon Sep 17 00:00:00 2001 From: Max Wang Date: Sun, 16 Jul 2017 16:59:28 -0700 Subject: [PATCH 1/5] fix SIMULATE_WEB_RESPONSE not imported #449 --- examples/ASCollectionView/Sample/ViewController.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/ASCollectionView/Sample/ViewController.m b/examples/ASCollectionView/Sample/ViewController.m index d00ea60df..4e6a04e97 100644 --- a/examples/ASCollectionView/Sample/ViewController.m +++ b/examples/ASCollectionView/Sample/ViewController.m @@ -16,7 +16,7 @@ // #import "ViewController.h" - +#import "AppDelegate.h" #import #import "SupplementaryNode.h" #import "ItemNode.h" @@ -70,8 +70,8 @@ - (void)viewDidLoad { NSLog(@"ViewController is not nil"); strongSelf->_data = [[NSArray alloc] init]; - [strongSelf->_collectionView performBatchUpdates:^{ - [strongSelf->_collectionView insertSections:[[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, 100)]]; + [strongSelf->_collectionNode performBatchUpdates:^{ + [strongSelf->_collectionNode insertSections:[[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, 100)]]; } completion:nil]; NSLog(@"ViewController finished updating collectionView"); } @@ -81,7 +81,7 @@ - (void)viewDidLoad }; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), mockWebService); - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.navigationController popViewControllerAnimated:YES]; }); #endif From 329f35ff56aa6605ba9157174568cfd18a7a5373 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Mon, 5 Feb 2018 14:10:21 -0800 Subject: [PATCH 2/5] Fix to make rangeMode update in right time --- Source/ASCollectionView.mm | 1 + Source/ASTableView.mm | 1 + Source/Details/ASRangeController.h | 5 +++++ Source/Details/ASRangeController.mm | 14 ++++++++++---- Tests/ASCollectionViewTests.mm | 1 + 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 0c706faa4..7c20dbddf 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -1511,6 +1511,7 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView // If a scroll happenes the current range mode needs to go to full ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; if (ASInterfaceStateIncludesVisible(interfaceState)) { + _rangeController.contentOffsetHasChanged = YES; [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; [self _checkForBatchFetching]; } diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 4606726f2..7bcd0fd5c 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -1218,6 +1218,7 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView // If a scroll happenes the current range mode needs to go to full ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; if (ASInterfaceStateIncludesVisible(interfaceState)) { + _rangeController.contentOffsetHasChanged = YES; [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; [self _checkForBatchFetching]; } diff --git a/Source/Details/ASRangeController.h b/Source/Details/ASRangeController.h index 46a5cbf69..152c4e8b5 100644 --- a/Source/Details/ASRangeController.h +++ b/Source/Details/ASRangeController.h @@ -100,6 +100,11 @@ AS_SUBCLASSING_RESTRICTED */ @property (nonatomic, weak) id delegate; +/** + * Property that indicates whether the scroll view for this range controller has ever changed its contentOffset. + */ +@property (nonatomic, assign) BOOL contentOffsetHasChanged; + @end diff --git a/Source/Details/ASRangeController.mm b/Source/Details/ASRangeController.mm index 818fb3a1d..4d10838b7 100644 --- a/Source/Details/ASRangeController.mm +++ b/Source/Details/ASRangeController.mm @@ -45,6 +45,7 @@ @interface ASRangeController () NSSet *_allPreviousIndexPaths; NSHashTable *_visibleNodes; ASLayoutRangeMode _currentRangeMode; + BOOL _contentOffsetHasChanged; BOOL _preserveCurrentRangeMode; BOOL _didRegisterForNodeDisplayNotifications; CFTimeInterval _pendingDisplayNodesTimestamp; @@ -77,6 +78,7 @@ - (instancetype)init _rangeIsValid = YES; _currentRangeMode = ASLayoutRangeModeUnspecified; + _contentOffsetHasChanged = NO; _preserveCurrentRangeMode = NO; _previousScrollDirection = ASScrollDirectionDown | ASScrollDirectionRight; @@ -237,9 +239,13 @@ - (void)_updateVisibleNodeIndexPaths ASInterfaceState selfInterfaceState = [self interfaceState]; ASLayoutRangeMode rangeMode = _currentRangeMode; - // If the range mode is explicitly set via updateCurrentRangeWithMode: it will last in that mode until the - // range controller becomes visible again or explicitly changes the range mode again - if ((!_preserveCurrentRangeMode && ASInterfaceStateIncludesVisible(selfInterfaceState)) || [[self class] isFirstRangeUpdateForRangeMode:rangeMode]) { + BOOL updateRangeMode = !_preserveCurrentRangeMode && _contentOffsetHasChanged; + + // If we've never scrolled before, we never update the range mode, so it doesn't jump into Full too early. + // This can happen if we have multiple, noisy updates occurring from application code before the user has engaged. + // If the range mode is explicitly set via updateCurrentRangeWithMode:, we'll preserve that for at least one update cycle. + // Once the user has scrolled and the range is visible, we'll always resume managing the range mode automatically. + if ((updateRangeMode && ASInterfaceStateIncludesVisible(selfInterfaceState)) || [[self class] isFirstRangeUpdateForRangeMode:rangeMode]) { rangeMode = [ASRangeController rangeModeForInterfaceState:selfInterfaceState currentRangeMode:_currentRangeMode]; } @@ -412,7 +418,7 @@ - (void)_updateVisibleNodeIndexPaths // NSLog(@"custom: %@", visibleNodePathsSet); // } [modifiedIndexPaths sortUsingSelector:@selector(compare:)]; - NSLog(@"Range update complete; modifiedIndexPaths: %@", [self descriptionWithIndexPaths:modifiedIndexPaths]); + NSLog(@"Range update complete; modifiedIndexPaths: %@, rangeMode: %d", [self descriptionWithIndexPaths:modifiedIndexPaths], rangeMode); #endif ASSignpostEnd(ASSignpostRangeControllerUpdate); diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index b223e246f..11ab711d6 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -1071,6 +1071,7 @@ - (void)testInitialRangeBounds for (NSInteger i = 0; i < c; i++) { NSIndexPath *ip = [NSIndexPath indexPathForItem:i inSection:s]; ASCellNode *node = [cn nodeForItemAtIndexPath:ip]; + [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]]; if (node.inPreloadState) { CGRect frame = [cn.view layoutAttributesForItemAtIndexPath:ip].frame; r = CGRectUnion(r, frame); From 58c39462b3b80007f1d267346cfe5f7deb83d4de Mon Sep 17 00:00:00 2001 From: Max Wang Date: Wed, 11 Apr 2018 22:16:41 -0700 Subject: [PATCH 3/5] Match interface update closer to UIKit. --- Source/ASCollectionView.mm | 14 +++++++++++--- Source/ASTableView.mm | 14 +++++++++++--- Source/Details/ASRangeController.h | 5 +++++ Source/Details/ASRangeController.mm | 10 +++++++--- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 72d8b78d4..44a6ca6e0 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -2273,15 +2273,23 @@ - (void)didMoveToWindow { BOOL visible = (self.window != nil); ASDisplayNode *node = self.collectionNode; + BOOL rangeControllerUpdated = NO; + if (!visible && node.inHierarchy) { + if (![node supportsRangeManagedInterfaceState]) { + rangeControllerUpdated = YES; + // Exit CellNodes first before Collection to match UIKit behaviors (tear down bottom up). + // Although we have not yet cleared the interfaceState's Visible bit (this happens in __exitHierarchy), + // the ASRangeController will get the correct value from -interfaceStateForRangeController:. + [_rangeController updateRanges]; + } [node __exitHierarchy]; } // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their // their update in the layout pass - if (![node supportsRangeManagedInterfaceState]) { - [_rangeController setNeedsUpdate]; - [_rangeController updateIfNeeded]; + if (![node supportsRangeManagedInterfaceState] && !rangeControllerUpdated) { + [_rangeController updateRanges]; } // When we aren't visible, we will only fetch up to the visible area. Now that we are visible, diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 58b3d033d..4a3d0afa2 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -1907,15 +1907,23 @@ - (void)didMoveToWindow { BOOL visible = (self.window != nil); ASDisplayNode *node = self.tableNode; + BOOL rangeControllerUpdated = NO; + if (!visible && node.inHierarchy) { + if (![node supportsRangeManagedInterfaceState]) { + rangeControllerUpdated = YES; + // Exit CellNodes first before Table to match UIKit behaviors (tear down bottom up). + // Although we have not yet cleared the interfaceState's Visible bit (this happens in __exitHierarchy), + // the ASRangeController will get the correct value from -interfaceStateForRangeController:. + [_rangeController updateRanges]; + } [node __exitHierarchy]; } // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their // their update in the layout pass - if (![node supportsRangeManagedInterfaceState]) { - [_rangeController setNeedsUpdate]; - [_rangeController updateIfNeeded]; + if (![node supportsRangeManagedInterfaceState] && !rangeControllerUpdated) { + [_rangeController updateRanges]; } // When we aren't visible, we will only fetch up to the visible area. Now that we are visible, diff --git a/Source/Details/ASRangeController.h b/Source/Details/ASRangeController.h index ca47ef862..29870b6be 100644 --- a/Source/Details/ASRangeController.h +++ b/Source/Details/ASRangeController.h @@ -64,6 +64,11 @@ AS_SUBCLASSING_RESTRICTED */ - (void)updateIfNeeded; +/** + * Force update the ranges immediately. + */ +- (void)updateRanges; + /** * Add the sized node for `indexPath` as a subview of `contentView`. * diff --git a/Source/Details/ASRangeController.mm b/Source/Details/ASRangeController.mm index 3bb404428..0ef8cc870 100644 --- a/Source/Details/ASRangeController.mm +++ b/Source/Details/ASRangeController.mm @@ -154,12 +154,16 @@ - (void)setNeedsUpdate - (void)updateIfNeeded { if (_needsRangeUpdate) { - _needsRangeUpdate = NO; - - [self _updateVisibleNodeIndexPaths]; + [self updateRanges]; } } +- (void)updateRanges +{ + _needsRangeUpdate = NO; + [self _updateVisibleNodeIndexPaths]; +} + - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode { _preserveCurrentRangeMode = YES; From 8c11accd450ad19dc8b0683125be3df6365dc8cc Mon Sep 17 00:00:00 2001 From: Max Wang Date: Tue, 17 Apr 2018 14:58:26 -0700 Subject: [PATCH 4/5] allow the correct exiting sequence for thrashing --- Source/Details/ASRangeController.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Details/ASRangeController.mm b/Source/Details/ASRangeController.mm index 0ef8cc870..abd56cc36 100644 --- a/Source/Details/ASRangeController.mm +++ b/Source/Details/ASRangeController.mm @@ -380,7 +380,7 @@ - (void)_updateVisibleNodeIndexPaths [newVisibleNodes addObject:node]; } // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. - if (node.interfaceState != interfaceState) { + if (node.pendingInterfaceState != interfaceState) { #if ASRangeControllerLoggingEnabled [modifiedIndexPaths addObject:indexPath]; #endif From 565fcbbe3670fbc71eb20edfad1b32738552d0c5 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Tue, 17 Apr 2018 15:07:59 -0700 Subject: [PATCH 5/5] refactor --- Source/ASCollectionView.mm | 8 ++++---- Source/ASTableView.mm | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 44a6ca6e0..65f2ecf9b 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -2273,11 +2273,11 @@ - (void)didMoveToWindow { BOOL visible = (self.window != nil); ASDisplayNode *node = self.collectionNode; - BOOL rangeControllerUpdated = NO; + BOOL rangeControllerNeedsUpdate = ![node supportsRangeManagedInterfaceState];; if (!visible && node.inHierarchy) { - if (![node supportsRangeManagedInterfaceState]) { - rangeControllerUpdated = YES; + if (rangeControllerNeedsUpdate) { + rangeControllerNeedsUpdate = NO; // Exit CellNodes first before Collection to match UIKit behaviors (tear down bottom up). // Although we have not yet cleared the interfaceState's Visible bit (this happens in __exitHierarchy), // the ASRangeController will get the correct value from -interfaceStateForRangeController:. @@ -2288,7 +2288,7 @@ - (void)didMoveToWindow // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their // their update in the layout pass - if (![node supportsRangeManagedInterfaceState] && !rangeControllerUpdated) { + if (rangeControllerNeedsUpdate) { [_rangeController updateRanges]; } diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 4a3d0afa2..c53e7eeb2 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -1907,11 +1907,11 @@ - (void)didMoveToWindow { BOOL visible = (self.window != nil); ASDisplayNode *node = self.tableNode; - BOOL rangeControllerUpdated = NO; + BOOL rangeControllerNeedsUpdate = ![node supportsRangeManagedInterfaceState];; if (!visible && node.inHierarchy) { - if (![node supportsRangeManagedInterfaceState]) { - rangeControllerUpdated = YES; + if (rangeControllerNeedsUpdate) { + rangeControllerNeedsUpdate = NO; // Exit CellNodes first before Table to match UIKit behaviors (tear down bottom up). // Although we have not yet cleared the interfaceState's Visible bit (this happens in __exitHierarchy), // the ASRangeController will get the correct value from -interfaceStateForRangeController:. @@ -1922,7 +1922,7 @@ - (void)didMoveToWindow // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their // their update in the layout pass - if (![node supportsRangeManagedInterfaceState] && !rangeControllerUpdated) { + if (rangeControllerNeedsUpdate) { [_rangeController updateRanges]; }