Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.

Commit a4c6d89

Browse files
author
Adlai Holler
authored
Don't Skip Remeasurement On First Layout Pass (#3031)
* Don't Skip Remeasurement if Initial Bounds are Zero * Remove misleading unit test
1 parent fab98b3 commit a4c6d89

3 files changed

Lines changed: 22 additions & 68 deletions

File tree

AsyncDisplayKit/ASCollectionView.mm

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ @interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDe
8989
ASBatchContext *_batchContext;
9090

9191
CGSize _lastBoundsSizeUsedForMeasuringNodes;
92-
BOOL _ignoreNextBoundsSizeChangeForMeasuringNodes;
9392

9493
NSMutableSet *_registeredSupplementaryKinds;
9594

@@ -272,9 +271,6 @@ - (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionV
272271
_superIsPendingDataLoad = YES;
273272

274273
_lastBoundsSizeUsedForMeasuringNodes = self.bounds.size;
275-
// If the initial size is 0, expect a size change very soon which is part of the initial configuration
276-
// and should not trigger a relayout.
277-
_ignoreNextBoundsSizeChangeForMeasuringNodes = CGSizeEqualToSize(_lastBoundsSizeUsedForMeasuringNodes, CGSizeZero);
278274

279275
_layoutFacilitator = layoutFacilitator;
280276

@@ -1904,31 +1900,22 @@ - (void)layer:(CALayer *)layer didChangeBoundsWithOldValue:(CGRect)oldBounds new
19041900
}
19051901
_lastBoundsSizeUsedForMeasuringNodes = newBounds.size;
19061902

1907-
// First size change occurs during initial configuration. An expensive relayout pass is unnecessary at that time
1908-
// and should be avoided, assuming that the initial data loading automatically runs shortly afterward.
1909-
if (_ignoreNextBoundsSizeChangeForMeasuringNodes) {
1910-
_ignoreNextBoundsSizeChangeForMeasuringNodes = NO;
1911-
} else {
1912-
// Laying out all nodes is expensive, and performing an empty update may be unsafe
1913-
// if the data source has pending changes that it hasn't reported yet – the collection
1914-
// view will requery the new counts and expect them to match the previous counts.
1915-
//
1916-
// We only need to do this if the bounds changed in the non-scrollable direction.
1917-
// If, for example, a vertical flow layout has its height changed due to a status bar
1918-
// appearance update, we do not need to relayout all nodes.
1919-
// For a more permanent fix to the unsafety mentioned above, see https://github.com/facebook/AsyncDisplayKit/pull/2182
1920-
ASScrollDirection scrollDirection = self.scrollableDirections;
1921-
BOOL fixedVertically = (ASScrollDirectionContainsVerticalDirection(scrollDirection) == NO);
1922-
BOOL fixedHorizontally = (ASScrollDirectionContainsHorizontalDirection(scrollDirection) == NO);
1923-
1924-
BOOL changedInNonScrollingDirection = (fixedHorizontally && newBounds.size.width != lastUsedSize.width) || (fixedVertically && newBounds.size.height != lastUsedSize.height);
1925-
1926-
if (changedInNonScrollingDirection) {
1927-
[_dataController relayoutAllNodes];
1928-
[_dataController waitUntilAllUpdatesAreCommitted];
1929-
// We need to ensure the size requery is done before we update our layout.
1930-
[self.collectionViewLayout invalidateLayout];
1931-
}
1903+
// Laying out all nodes is expensive.
1904+
// We only need to do this if the bounds changed in the non-scrollable direction.
1905+
// If, for example, a vertical flow layout has its height changed due to a status bar
1906+
// appearance update, we do not need to relayout all nodes.
1907+
// For a more permanent fix to the unsafety mentioned above, see https://github.com/facebook/AsyncDisplayKit/pull/2182
1908+
ASScrollDirection scrollDirection = self.scrollableDirections;
1909+
BOOL fixedVertically = (ASScrollDirectionContainsVerticalDirection(scrollDirection) == NO);
1910+
BOOL fixedHorizontally = (ASScrollDirectionContainsHorizontalDirection(scrollDirection) == NO);
1911+
1912+
BOOL changedInNonScrollingDirection = (fixedHorizontally && newBounds.size.width != lastUsedSize.width) || (fixedVertically && newBounds.size.height != lastUsedSize.height);
1913+
1914+
if (changedInNonScrollingDirection) {
1915+
[_dataController relayoutAllNodes];
1916+
[_dataController waitUntilAllUpdatesAreCommitted];
1917+
// We need to ensure the size requery is done before we update our layout.
1918+
[self.collectionViewLayout invalidateLayout];
19321919
}
19331920
}
19341921

AsyncDisplayKit/ASTableView.mm

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
#import <AsyncDisplayKit/ASTableView+Undeprecated.h>
3030
#import <AsyncDisplayKit/ASBatchContext.h>
3131

32-
static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero};
3332
static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
3433

3534
//#define LOG(...) NSLog(__VA_ARGS__)
@@ -144,7 +143,6 @@ @interface ASTableView () <ASRangeControllerDataSource, ASRangeControllerDelegat
144143
CALayer *_retainedLayer;
145144

146145
CGFloat _nodesConstrainedWidth;
147-
BOOL _ignoreNodesConstrainedWidthChange;
148146
BOOL _queuedNodeHeightUpdate;
149147
BOOL _isDeallocating;
150148
BOOL _performingBatchUpdates;
@@ -268,9 +266,6 @@ - (void)configureWithDataControllerClass:(Class)dataControllerClass eventLog:(AS
268266
_automaticallyAdjustsContentOffset = NO;
269267

270268
_nodesConstrainedWidth = self.bounds.size.width;
271-
// If the initial size is 0, expect a size change very soon which is part of the initial configuration
272-
// and should not trigger a relayout.
273-
_ignoreNodesConstrainedWidthChange = (_nodesConstrainedWidth == 0);
274269

275270
_proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self];
276271
super.delegate = (id<UITableViewDelegate>)_proxyDelegate;
@@ -690,19 +685,14 @@ - (void)waitUntilAllUpdatesAreCommitted
690685

691686
- (void)layoutSubviews
692687
{
688+
// Remeasure all rows if our row width has changed.
693689
CGFloat constrainedWidth = self.bounds.size.width - [self sectionIndexWidth];
694-
if (_nodesConstrainedWidth != constrainedWidth) {
690+
if (constrainedWidth > 0 && _nodesConstrainedWidth != constrainedWidth) {
695691
_nodesConstrainedWidth = constrainedWidth;
696692

697-
// First width change occurs during initial configuration. An expensive relayout pass is unnecessary at that time
698-
// and should be avoided, assuming that the initial data loading automatically runs shortly afterward.
699-
if (_ignoreNodesConstrainedWidthChange) {
700-
_ignoreNodesConstrainedWidthChange = NO;
701-
} else {
702-
[self beginUpdates];
703-
[_dataController relayoutAllNodes];
704-
[self endUpdatesAnimated:(ASDisplayNodeLayerHasAnimations(self.layer) == NO) completion:nil];
705-
}
693+
[self beginUpdates];
694+
[_dataController relayoutAllNodes];
695+
[self endUpdatesAnimated:(ASDisplayNodeLayerHasAnimations(self.layer) == NO) completion:nil];
706696
}
707697

708698
// To ensure _nodesConstrainedWidth is up-to-date for every usage, this call to super must be done last
@@ -1613,7 +1603,7 @@ - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAt
16131603

16141604
- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
16151605
{
1616-
ASSizeRange constrainedSize = kInvalidSizeRange;
1606+
ASSizeRange constrainedSize = ASSizeRangeZero;
16171607
if (_asyncDelegateFlags.tableNodeConstrainedSizeForRow) {
16181608
GET_TABLENODE_OR_RETURN(tableNode, constrainedSize);
16191609
ASSizeRange delegateConstrainedSize = [_asyncDelegate tableNode:tableNode constrainedSizeForRowAtIndexPath:indexPath];

AsyncDisplayKitTests/ASTableViewTests.mm

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -395,29 +395,6 @@ - (void)testRelayoutAllNodesWithNonZeroSizeInitially
395395
[self triggerSizeChangeAndAssertRelayoutAllNodesForTableView:tableView newSize:tableViewFinalSize];
396396
}
397397

398-
- (void)testRelayoutAllNodesWithZeroSizeInitially
399-
{
400-
// Initial width of the table view is 0. The first size change is part of the initial config.
401-
// Any subsequence size change after that must trigger a relayout.
402-
CGSize tableViewFinalSize = CGSizeMake(100, 500);
403-
ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectZero
404-
style:UITableViewStylePlain];
405-
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
406-
407-
tableView.asyncDelegate = dataSource;
408-
tableView.asyncDataSource = dataSource;
409-
410-
// Initial configuration
411-
UIView *superview = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
412-
[superview addSubview:tableView];
413-
// Width and height are swapped so that a later size change will simulate a rotation
414-
tableView.frame = CGRectMake(0, 0, tableViewFinalSize.height, tableViewFinalSize.width);
415-
[tableView layoutIfNeeded];
416-
417-
XCTAssertEqual(tableView.testDataController.numberOfAllNodesRelayouts, 0);
418-
[self triggerSizeChangeAndAssertRelayoutAllNodesForTableView:tableView newSize:tableViewFinalSize];
419-
}
420-
421398
- (void)testRelayoutVisibleRowsWhenEditingModeIsChanged
422399
{
423400
CGSize tableViewSize = CGSizeMake(100, 500);

0 commit comments

Comments
 (0)