diff --git a/AsyncDisplayKit/ASContextTransitioning.h b/AsyncDisplayKit/ASContextTransitioning.h index 0eb5d42bae..37f2ae6b98 100644 --- a/AsyncDisplayKit/ASContextTransitioning.h +++ b/AsyncDisplayKit/ASContextTransitioning.h @@ -6,10 +6,15 @@ // Copyright © 2016 Facebook. All rights reserved. // -#import +#import @protocol ASContextTransitioning +/** + @abstreact Defines if the given transition is animated + */ +- (BOOL)isAnimated; + /** @abstract The frame for the given node before the transition began. @discussion Returns CGRectNull if the node was not in the hierarchy before the transition. @@ -22,10 +27,8 @@ */ - (CGRect)finalFrameForNode:(ASDisplayNode *)node; -- (NSArray *)sublayouts; - /** - @abstract Invoke this method when the transition is completed in `transitionLayout:` + @abstract Invoke this method when the transition is completed in `animateLayoutTransition:` @discussion Passing NO to `didComplete` will set the original layout as the new layout. */ - (void)completeTransition:(BOOL)didComplete; diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index f9195853d5..e571503b81 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -12,9 +12,9 @@ #import #import #import +#import @class ASLayoutSpec; -@protocol ASContextTransitioning; NS_ASSUME_NONNULL_BEGIN @@ -158,20 +158,15 @@ NS_ASSUME_NONNULL_BEGIN /** @name Layout Transitioning */ -/** - @discussion Called right before new nodes are inserted. A great place to setup layer attributes before animation. - */ -- (void)willTransitionLayout:(id)context; - /** @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy. */ -- (void)transitionLayout:(id)context; +- (void)animateLayoutTransition:(id)context; /** @discussion A place to clean up your nodes after the transition */ -- (void)didCompleteTransitionLayout:(id)context; +- (void)didCompleteLayoutTransition:(id)context; /** @name Drawing */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 2e80e73be0..ce60b8cc01 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -658,12 +658,17 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [_layout.sublayouts asdk_diffWithArray:newLayout.sublayouts insertions:&insertions deletions:&deletions compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); }]; - _insertedSubnodes = [self _filterNodesInLayouts:newLayout.sublayouts withIndexes:insertions]; - _deletedSubnodes = [self _filterNodesInLayouts:_layout.sublayouts withIndexes:deletions]; + _insertedSubnodes = [self _nodesInLayout:newLayout atIndexes:insertions]; + _deletedSubnodes = [self _nodesInLayout:_layout atIndexes:deletions offsetIndexes:insertions]; } else { NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.sublayouts count])]; - _insertedSubnodes = [self _filterNodesInLayouts:newLayout.sublayouts withIndexes:indexes]; - _deletedSubnodes = @[]; + _insertedSubnodes = [self _nodesInLayout:newLayout atIndexes:indexes]; + _deletedSubnodes = nil; + } + + if (!_deferImmediateHierarchyManagement) { + [self __implicitlyInsertSubnodes]; + [self __implicitlyRemoveSubnodes]; } } @@ -693,14 +698,43 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return _layout; } -- (NSArray<_ASDisplayNodePosition *> *)_filterNodesInLayouts:(NSArray *)layouts withIndexes:(NSIndexSet *)indexes +/** + @abstract Retrieves nodes at the given indexes from the layout's sublayouts + */ +- (NSArray<_ASDisplayNodePosition *> *)_nodesInLayout:(ASLayout *)layout atIndexes:(NSIndexSet *)indexes { NSMutableArray<_ASDisplayNodePosition *> *result = [NSMutableArray array]; - [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - ASDisplayNode *node = (ASDisplayNode *)layouts[idx].layoutableObject; + NSInteger idx = [indexes firstIndex]; + while (idx != NSNotFound) { + ASDisplayNode *node = (ASDisplayNode *)layout.sublayouts[idx].layoutableObject; ASDisplayNodeAssertNotNil(node, @"A flattened layout must consist exclusively of node sublayouts"); [result addObject:[_ASDisplayNodePosition positionWithNode:node atIndex:idx]]; - }]; + idx = [indexes indexGreaterThanIndex:idx]; + } + return result; +} + +/** + @abstract Retrieves nodes at the given indexes from the layout's sublayouts, shifting the target index such that insertions can happen before deletions + */ +- (NSArray<_ASDisplayNodePosition *> *)_nodesInLayout:(ASLayout *)layout atIndexes:(NSIndexSet *)indexes offsetIndexes:(NSIndexSet *)offsets +{ + NSMutableArray<_ASDisplayNodePosition *> *result = [NSMutableArray array]; + NSInteger offset = 0; + NSInteger offsetIndex = -1; + NSInteger idx = [indexes firstIndex]; + while (idx != NSNotFound) { + ASDisplayNode *node = (ASDisplayNode *)layout.sublayouts[idx].layoutableObject; + ASDisplayNodeAssertNotNil(node, @"A flattened layout must consist exclusively of node sublayouts"); + // Offset deletions such that insertions can be performed first + NSInteger j = [offsets indexLessThanOrEqualToIndex:offsetIndex]; + if (j != NSNotFound && offsetIndex < j) { + offset++; + offsetIndex = j; + } + [result addObject:[_ASDisplayNodePosition positionWithNode:node atIndex:idx + offset]]; + idx = [indexes indexGreaterThanIndex:idx]; + } return result; } @@ -1000,82 +1034,58 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo - (void)transitionLayoutWithAnimation:(BOOL)animated { - [self transitionLayoutToFit:_constrainedSize animated:animated]; + [self transitionLayoutThatFits:_constrainedSize animated:animated]; } -- (void)transitionLayoutToFit:(ASSizeRange)constrainedSize animated:(BOOL)animated +- (void)transitionLayoutThatFits:(ASSizeRange)constrainedSize animated:(BOOL)animated { [self invalidateCalculatedLayout]; + _deferImmediateHierarchyManagement = YES; [self measureWithSizeRange:constrainedSize]; // Generate a new layout + _deferImmediateHierarchyManagement = NO; [self __transitionLayoutWithAnimation:animated]; } - (void)__transitionLayoutWithAnimation:(BOOL)animated { _transitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated delegate:self]; - - [self willTransitionLayout:_transitionContext]; - if ([_insertedSubnodes count]) { - for (_ASDisplayNodePosition *position in _insertedSubnodes) { - [self _implicitlyInsertSubnode:position.node atIndex:position.index]; - } - _insertedSubnodes = @[]; - } - - [self transitionLayout:_transitionContext]; + [self __implicitlyInsertSubnodes]; + [self animateLayoutTransition:_transitionContext]; } -- (void)willTransitionLayout:(id)context +- (void)animateLayoutTransition:(id)context { -} - -- (void)transitionLayout:(id)context -{ - for (ASLayout *subnodeLayout in [context sublayouts]) { - ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [self _adjustedFrameForLayout:subnodeLayout]; - } - + [self __layoutSublayouts]; [context completeTransition:YES]; } - (void)didCompleteTransitionLayout:(id)context +{ + [self __implicitlyRemoveSubnodes]; +} + +#pragma mark - Implicit node hierarchy managagment + +- (void)__implicitlyInsertSubnodes +{ + if ([_insertedSubnodes count]) { + for (_ASDisplayNodePosition *position in _insertedSubnodes) { + [self _implicitlyInsertSubnode:position.node atIndex:position.index]; + } + _insertedSubnodes = nil; + } +} + +- (void)__implicitlyRemoveSubnodes { if ([_deletedSubnodes count]) { for (_ASDisplayNodePosition *position in _deletedSubnodes) { [self _implicitlyRemoveSubnode:position.node atIndex:position.index]; } - _deletedSubnodes = @[]; + _deletedSubnodes = nil; } } -- (CGRect)_adjustedFrameForLayout:(ASLayout *)layout -{ - CGRect subnodeFrame = CGRectZero; - CGPoint adjustedOrigin = layout.position; - if (isfinite(adjustedOrigin.x) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); - adjustedOrigin.x = 0; - } - if (isfinite(adjustedOrigin.y) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); - adjustedOrigin.y = 0; - } - subnodeFrame.origin = adjustedOrigin; - - CGSize adjustedSize = layout.size; - if (isfinite(adjustedSize.width) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid size"); - adjustedSize.width = 0; - } - if (isfinite(adjustedSize.height) == NO) { - ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); - adjustedSize.height = 0; - } - subnodeFrame.size = adjustedSize; - - return subnodeFrame; -} - #pragma mark - _ASTransitionContextDelegate - (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete @@ -1104,11 +1114,6 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo return CGRectNull; } -- (NSArray *)sublayoutsForTransitioningContext:(_ASTransitionContext *)context -{ - return _layout.sublayouts; -} - #pragma mark - _ASDisplayLayerDelegate - (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer @@ -2168,7 +2173,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) } if ([[self class] usesImplicitHierarchyManagement]) { - [self __transitionLayoutWithAnimation:NO]; + [self __layoutSublayouts]; } else { // Assume that _layout was flattened and is 1-level deep. CGRect subnodeFrame = CGRectZero; @@ -2180,6 +2185,42 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) } } +- (void)__layoutSublayouts +{ + for (ASLayout *subnodeLayout in _layout.sublayouts) { + ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [self _adjustedFrameForLayout:subnodeLayout]; + } +} + +- (CGRect)_adjustedFrameForLayout:(ASLayout *)layout +{ + CGRect subnodeFrame = CGRectZero; + CGPoint adjustedOrigin = layout.position; + if (isfinite(adjustedOrigin.x) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); + adjustedOrigin.x = 0; + } + if (isfinite(adjustedOrigin.y) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); + adjustedOrigin.y = 0; + } + subnodeFrame.origin = adjustedOrigin; + + CGSize adjustedSize = layout.size; + if (isfinite(adjustedSize.width) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid size"); + adjustedSize.width = 0; + } + if (isfinite(adjustedSize.height) == NO) { + ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position"); + adjustedSize.height = 0; + } + subnodeFrame.size = adjustedSize; + + NSLog(@"Adjusted frame: %@", NSStringFromCGRect(subnodeFrame)); + return subnodeFrame; +} + - (void)_implicitlyInsertSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx { [self insertSubnode:node atIndex:idx]; diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index ca3dd3774f..9affcc7b37 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -54,6 +54,7 @@ #import #import #import +#import #import #import #import @@ -68,4 +69,3 @@ #import #import #import -#import diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 38e86b5731..336069e62c 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -66,6 +66,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo NSMutableArray *_subnodes; _ASTransitionContext *_transitionContext; + BOOL _deferImmediateHierarchyManagement; NSArray<_ASDisplayNodePosition *> *_insertedSubnodes; NSArray<_ASDisplayNodePosition *> *_deletedSubnodes; diff --git a/AsyncDisplayKit/_ASTransitionContext.h b/AsyncDisplayKit/_ASTransitionContext.h index 2c964a99c5..ab396514cb 100644 --- a/AsyncDisplayKit/_ASTransitionContext.h +++ b/AsyncDisplayKit/_ASTransitionContext.h @@ -19,8 +19,6 @@ - (CGRect)transitionContext:(_ASTransitionContext *)context initialFrameForNode:(ASDisplayNode *)node; - (CGRect)transitionContext:(_ASTransitionContext *)context finalFrameForNode:(ASDisplayNode *)node; -- (NSArray *)sublayoutsForTransitioningContext:(_ASTransitionContext *)context; - @end @interface _ASTransitionContext : NSObject diff --git a/AsyncDisplayKit/_ASTransitionContext.m b/AsyncDisplayKit/_ASTransitionContext.m index e5cf012d26..059138e6ae 100644 --- a/AsyncDisplayKit/_ASTransitionContext.m +++ b/AsyncDisplayKit/_ASTransitionContext.m @@ -36,11 +36,6 @@ return [_delegate transitionContext:self finalFrameForNode:node]; } -- (NSArray *)sublayouts -{ - return [_delegate sublayoutsForTransitioningContext:self]; -} - - (void)completeTransition:(BOOL)didComplete { [_delegate transitionContext:self didComplete:didComplete];