From 62d7e14ce1126b87a09563732edca62b6c92baa9 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 3 Mar 2017 09:24:04 -0800 Subject: [PATCH] Improve measurement code for cell nodes (#3119) --- Source/ASCellNode.mm | 41 +------------------ Source/ASCollectionView.mm | 10 +++++ Source/ASDisplayNode.mm | 13 ++++++ Source/ASTableView.mm | 16 ++++++++ Source/Details/ASDataController.h | 6 +++ Source/Details/ASDataController.mm | 5 +-- .../Private/ASDisplayNode+FrameworkPrivate.h | 14 +++---- 7 files changed, 55 insertions(+), 50 deletions(-) diff --git a/Source/ASCellNode.mm b/Source/ASCellNode.mm index e215197950..abfc25bc53 100644 --- a/Source/ASCellNode.mm +++ b/Source/ASCellNode.mm @@ -125,47 +125,10 @@ } } -- (void)transitionLayoutWithAnimation:(BOOL)animated - shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(void(^)())completion -{ - CGSize oldSize = self.calculatedSize; - [super transitionLayoutWithAnimation:animated - shouldMeasureAsync:shouldMeasureAsync - measurementCompletion:^{ - [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; - if (completion) { - completion(); - } - } - ]; -} - -- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize - animated:(BOOL)animated - shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(void(^)())completion -{ - CGSize oldSize = self.calculatedSize; - [super transitionLayoutWithSizeRange:constrainedSize - animated:animated - shouldMeasureAsync:shouldMeasureAsync - measurementCompletion:^{ - [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; - if (completion) { - completion(); - } - } - ]; -} - -- (void)didRelayoutFromOldSize:(CGSize)oldSize toNewSize:(CGSize)newSize +- (void)_layoutTransitionMeasurementDidFinish { if (_interactionDelegate != nil) { - ASPerformBlockOnMainThread(^{ - BOOL sizeChanged = !CGSizeEqualToSize(oldSize, newSize); - [_interactionDelegate nodeDidRelayout:self sizeChanged:sizeChanged]; - }); + [_interactionDelegate nodeDidInvalidateSize:self]; } } diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 9fa4432d0c..9c55d8a593 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -32,6 +32,7 @@ #import #import #import +#import /** * A macro to get self.collectionNode and assign it to a local variable, or return @@ -1563,6 +1564,15 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; } } +- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size +{ + NSIndexPath *indexPath = [self indexPathForNode:element.node]; + UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; + CGRect rect = attributes.frame; + return CGSizeEqualToSizeWithIn(rect.size, size, FLT_EPSILON); + +} + - (id)dataControllerEnvironment { return self.collectionNode; diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index aca89ea324..f1e134c800 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -1266,6 +1266,12 @@ ASLayoutElementFinalLayoutElementDefault return _calculatedDisplayNodeLayout->constrainedSize; } +/** + * @abstract Informs the root node that the intrinsic size of the receiver is no longer valid. + * + * @discussion The size of a root node is determined by each subnode. Calling invalidateSize will let the root node know + * that the intrinsic size of the receiver node is no longer valid and a resizing of the root node needs to happen. + */ - (void)setNeedsLayoutFromAbove { ASDisplayNodeAssertThreadAffinity(self); @@ -1487,6 +1493,8 @@ ASLayoutElementFinalLayoutElementDefault }); // Measurement pass completion + // Give the subclass a change to hook into before calling the completion block + [self _layoutTransitionMeasurementDidFinish]; if (completion) { completion(); } @@ -1560,6 +1568,11 @@ ASLayoutElementFinalLayoutElementDefault return _transitionID; } +- (void)_layoutTransitionMeasurementDidFinish +{ + // No-Op in ASDisplayNode +} + - (void)_finishOrCancelTransition { ASDN::MutexLocker l(__instanceLock__); diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index c1790f681a..3eb9b7fd8c 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -1695,6 +1695,22 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } +- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size +{ + NSIndexPath *indexPath = [self indexPathForNode:element.node]; + CGRect rect = [self rectForRowAtIndexPath:indexPath]; + + /** + * Weirdly enough, Apple expects the return value in tableView:heightForRowAtIndexPath: to _include_ the height + * of the separator, if there is one! So if rectForRow would return 44.0 we need to use 43.5. + */ + if (self.separatorStyle != UITableViewCellSeparatorStyleNone) { + rect.size.height -= 1.0 / ASScreenScale(); + } + + return (fabs(rect.size.height - size.height) < FLT_EPSILON); +} + #pragma mark - ASDataControllerEnvironmentDelegate - (id)dataControllerEnvironment diff --git a/Source/Details/ASDataController.h b/Source/Details/ASDataController.h index 4df24f38ee..201ccf2226 100644 --- a/Source/Details/ASDataController.h +++ b/Source/Details/ASDataController.h @@ -29,6 +29,7 @@ NS_ASSUME_NONNULL_BEGIN @class ASCellNode; @class ASDataController; @class ASElementMap; +@class ASCollectionElement; @class _ASHierarchyChangeSet; @protocol ASTraitEnvironment; @protocol ASSectionContext; @@ -65,6 +66,11 @@ extern NSString * const ASCollectionInvalidUpdateException; */ - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController; +/** + Returns if the collection element size matches a given size + */ +- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size; + @optional - (NSArray *)dataController:(ASDataController *)dataController supplementaryNodeKindsInSections:(NSIndexSet *)sections; diff --git a/Source/Details/ASDataController.mm b/Source/Details/ASDataController.mm index e6dba245d4..b19c5efb4e 100644 --- a/Source/Details/ASDataController.mm +++ b/Source/Details/ASDataController.mm @@ -640,10 +640,9 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray * NSString *kind = node.collectionElement.supplementaryElementKind ?: ASDataControllerRowNodeKind; NSIndexPath *indexPath = [_pendingMap indexPathForElement:node.collectionElement]; ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - - CGSize oldSize = node.bounds.size; [self _layoutNode:node withConstrainedSize:constrainedSize]; - if (! CGSizeEqualToSize(node.frame.size, oldSize)) { + BOOL matchesSize = [_dataSource dataController:self presentedSizeForElement:node.collectionElement matchesSize:node.frame.size]; + if (! matchesSize) { [nodesSizesChanged addObject:node]; } } diff --git a/Source/Private/ASDisplayNode+FrameworkPrivate.h b/Source/Private/ASDisplayNode+FrameworkPrivate.h index 64bc453493..aaa5b46b5b 100644 --- a/Source/Private/ASDisplayNode+FrameworkPrivate.h +++ b/Source/Private/ASDisplayNode+FrameworkPrivate.h @@ -214,20 +214,18 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat */ - (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState; -/** - * @abstract Informs the root node that the intrinsic size of the receiver is no longer valid. - * - * @discussion The size of a root node is determined by each subnode. Calling invalidateSize will let the root node know - * that the intrinsic size of the receiver node is no longer valid and a resizing of the root node needs to happen. - */ -- (void)setNeedsLayoutFromAbove; - /** * @abstract Subclass hook for nodes that are acting as root nodes. This method is called if one of the subnodes * size is invalidated and may need to result in a different size as the current calculated size. */ - (void)_locked_rootNodeDidInvalidateSize; +/** + * @abstract Subclass hook for nodes that are acting as root nodes. This method is called after measurement + * finished in a layout transition but before the measurement completion handler is called + */ +- (void)_layoutTransitionMeasurementDidFinish; + @end @interface UIView (ASDisplayNodeInternal)