Clean up implicit hierarchy management to enable custom animation

This commit is contained in:
Levi McCallum
2016-02-05 15:35:30 -08:00
parent 6a2903f2ec
commit 3b1a32c413
7 changed files with 117 additions and 84 deletions

View File

@@ -6,10 +6,15 @@
// Copyright © 2016 Facebook. All rights reserved. // Copyright © 2016 Facebook. All rights reserved.
// //
#import <AsyncDisplayKit/AsyncDisplayKit.h> #import <AsyncDisplayKit/ASDisplayNode.h>
@protocol ASContextTransitioning <NSObject> @protocol ASContextTransitioning <NSObject>
/**
@abstreact Defines if the given transition is animated
*/
- (BOOL)isAnimated;
/** /**
@abstract The frame for the given node before the transition began. @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. @discussion Returns CGRectNull if the node was not in the hierarchy before the transition.
@@ -22,10 +27,8 @@
*/ */
- (CGRect)finalFrameForNode:(ASDisplayNode *)node; - (CGRect)finalFrameForNode:(ASDisplayNode *)node;
- (NSArray<ASLayout *> *)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. @discussion Passing NO to `didComplete` will set the original layout as the new layout.
*/ */
- (void)completeTransition:(BOOL)didComplete; - (void)completeTransition:(BOOL)didComplete;

View File

@@ -12,9 +12,9 @@
#import <AsyncDisplayKit/ASAssert.h> #import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASDisplayNode.h> #import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASThread.h> #import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/ASContextTransitioning.h>
@class ASLayoutSpec; @class ASLayoutSpec;
@protocol ASContextTransitioning;
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@@ -158,20 +158,15 @@ NS_ASSUME_NONNULL_BEGIN
/** @name Layout Transitioning */ /** @name Layout Transitioning */
/**
@discussion Called right before new nodes are inserted. A great place to setup layer attributes before animation.
*/
- (void)willTransitionLayout:(id<ASContextTransitioning>)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. @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<ASContextTransitioning>)context; - (void)animateLayoutTransition:(id<ASContextTransitioning>)context;
/** /**
@discussion A place to clean up your nodes after the transition @discussion A place to clean up your nodes after the transition
*/ */
- (void)didCompleteTransitionLayout:(id<ASContextTransitioning>)context; - (void)didCompleteLayoutTransition:(id<ASContextTransitioning>)context;
/** @name Drawing */ /** @name Drawing */

View File

@@ -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) { [_layout.sublayouts asdk_diffWithArray:newLayout.sublayouts insertions:&insertions deletions:&deletions compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) {
return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject); return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject);
}]; }];
_insertedSubnodes = [self _filterNodesInLayouts:newLayout.sublayouts withIndexes:insertions]; _insertedSubnodes = [self _nodesInLayout:newLayout atIndexes:insertions];
_deletedSubnodes = [self _filterNodesInLayouts:_layout.sublayouts withIndexes:deletions]; _deletedSubnodes = [self _nodesInLayout:_layout atIndexes:deletions offsetIndexes:insertions];
} else { } else {
NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.sublayouts count])]; NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.sublayouts count])];
_insertedSubnodes = [self _filterNodesInLayouts:newLayout.sublayouts withIndexes:indexes]; _insertedSubnodes = [self _nodesInLayout:newLayout atIndexes:indexes];
_deletedSubnodes = @[]; _deletedSubnodes = nil;
}
if (!_deferImmediateHierarchyManagement) {
[self __implicitlyInsertSubnodes];
[self __implicitlyRemoveSubnodes];
} }
} }
@@ -693,14 +698,43 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return _layout; return _layout;
} }
- (NSArray<_ASDisplayNodePosition *> *)_filterNodesInLayouts:(NSArray<ASLayout *> *)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]; NSMutableArray<_ASDisplayNodePosition *> *result = [NSMutableArray array];
[indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { NSInteger idx = [indexes firstIndex];
ASDisplayNode *node = (ASDisplayNode *)layouts[idx].layoutableObject; while (idx != NSNotFound) {
ASDisplayNode *node = (ASDisplayNode *)layout.sublayouts[idx].layoutableObject;
ASDisplayNodeAssertNotNil(node, @"A flattened layout must consist exclusively of node sublayouts"); ASDisplayNodeAssertNotNil(node, @"A flattened layout must consist exclusively of node sublayouts");
[result addObject:[_ASDisplayNodePosition positionWithNode:node atIndex:idx]]; [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; return result;
} }
@@ -1000,82 +1034,58 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo
- (void)transitionLayoutWithAnimation:(BOOL)animated - (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]; [self invalidateCalculatedLayout];
_deferImmediateHierarchyManagement = YES;
[self measureWithSizeRange:constrainedSize]; // Generate a new layout [self measureWithSizeRange:constrainedSize]; // Generate a new layout
_deferImmediateHierarchyManagement = NO;
[self __transitionLayoutWithAnimation:animated]; [self __transitionLayoutWithAnimation:animated];
} }
- (void)__transitionLayoutWithAnimation:(BOOL)animated - (void)__transitionLayoutWithAnimation:(BOOL)animated
{ {
_transitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated delegate:self]; _transitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated delegate:self];
[self __implicitlyInsertSubnodes];
[self willTransitionLayout:_transitionContext]; [self animateLayoutTransition:_transitionContext];
if ([_insertedSubnodes count]) {
for (_ASDisplayNodePosition *position in _insertedSubnodes) {
[self _implicitlyInsertSubnode:position.node atIndex:position.index];
}
_insertedSubnodes = @[];
}
[self transitionLayout:_transitionContext];
} }
- (void)willTransitionLayout:(id<ASContextTransitioning>)context - (void)animateLayoutTransition:(id<ASContextTransitioning>)context
{ {
} [self __layoutSublayouts];
- (void)transitionLayout:(id<ASContextTransitioning>)context
{
for (ASLayout *subnodeLayout in [context sublayouts]) {
((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [self _adjustedFrameForLayout:subnodeLayout];
}
[context completeTransition:YES]; [context completeTransition:YES];
} }
- (void)didCompleteTransitionLayout:(id<ASContextTransitioning>)context - (void)didCompleteTransitionLayout:(id<ASContextTransitioning>)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]) { if ([_deletedSubnodes count]) {
for (_ASDisplayNodePosition *position in _deletedSubnodes) { for (_ASDisplayNodePosition *position in _deletedSubnodes) {
[self _implicitlyRemoveSubnode:position.node atIndex:position.index]; [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 #pragma mark - _ASTransitionContextDelegate
- (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete - (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete
@@ -1104,11 +1114,6 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo
return CGRectNull; return CGRectNull;
} }
- (NSArray<ASLayout *> *)sublayoutsForTransitioningContext:(_ASTransitionContext *)context
{
return _layout.sublayouts;
}
#pragma mark - _ASDisplayLayerDelegate #pragma mark - _ASDisplayLayerDelegate
- (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer - (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer
@@ -2168,7 +2173,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
} }
if ([[self class] usesImplicitHierarchyManagement]) { if ([[self class] usesImplicitHierarchyManagement]) {
[self __transitionLayoutWithAnimation:NO]; [self __layoutSublayouts];
} else { } else {
// Assume that _layout was flattened and is 1-level deep. // Assume that _layout was flattened and is 1-level deep.
CGRect subnodeFrame = CGRectZero; 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 - (void)_implicitlyInsertSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx
{ {
[self insertSubnode:node atIndex:idx]; [self insertSubnode:node atIndex:idx];

View File

@@ -54,6 +54,7 @@
#import <AsyncDisplayKit/_ASAsyncTransactionGroup.h> #import <AsyncDisplayKit/_ASAsyncTransactionGroup.h>
#import <AsyncDisplayKit/ASAvailability.h> #import <AsyncDisplayKit/ASAvailability.h>
#import <AsyncDisplayKit/ASCollectionViewLayoutController.h> #import <AsyncDisplayKit/ASCollectionViewLayoutController.h>
#import <AsyncDisplayKit/ASContextTransitioning.h>
#import <AsyncDisplayKit/ASControlNode+Subclasses.h> #import <AsyncDisplayKit/ASControlNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h> #import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeExtraIvars.h> #import <AsyncDisplayKit/ASDisplayNodeExtraIvars.h>
@@ -68,4 +69,3 @@
#import <AsyncDisplayKit/NSMutableAttributedString+TextKitAdditions.h> #import <AsyncDisplayKit/NSMutableAttributedString+TextKitAdditions.h>
#import <AsyncDisplayKit/UICollectionViewLayout+ASConvenience.h> #import <AsyncDisplayKit/UICollectionViewLayout+ASConvenience.h>
#import <AsyncDisplayKit/UIView+ASConvenience.h> #import <AsyncDisplayKit/UIView+ASConvenience.h>
#import <AsyncDisplayKit/ASContextTransitioning.h>

View File

@@ -66,6 +66,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
NSMutableArray *_subnodes; NSMutableArray *_subnodes;
_ASTransitionContext *_transitionContext; _ASTransitionContext *_transitionContext;
BOOL _deferImmediateHierarchyManagement;
NSArray<_ASDisplayNodePosition *> *_insertedSubnodes; NSArray<_ASDisplayNodePosition *> *_insertedSubnodes;
NSArray<_ASDisplayNodePosition *> *_deletedSubnodes; NSArray<_ASDisplayNodePosition *> *_deletedSubnodes;

View File

@@ -19,8 +19,6 @@
- (CGRect)transitionContext:(_ASTransitionContext *)context initialFrameForNode:(ASDisplayNode *)node; - (CGRect)transitionContext:(_ASTransitionContext *)context initialFrameForNode:(ASDisplayNode *)node;
- (CGRect)transitionContext:(_ASTransitionContext *)context finalFrameForNode:(ASDisplayNode *)node; - (CGRect)transitionContext:(_ASTransitionContext *)context finalFrameForNode:(ASDisplayNode *)node;
- (NSArray<ASLayout *> *)sublayoutsForTransitioningContext:(_ASTransitionContext *)context;
@end @end
@interface _ASTransitionContext : NSObject <ASContextTransitioning> @interface _ASTransitionContext : NSObject <ASContextTransitioning>

View File

@@ -36,11 +36,6 @@
return [_delegate transitionContext:self finalFrameForNode:node]; return [_delegate transitionContext:self finalFrameForNode:node];
} }
- (NSArray<ASLayout *> *)sublayouts
{
return [_delegate sublayoutsForTransitioningContext:self];
}
- (void)completeTransition:(BOOL)didComplete - (void)completeTransition:(BOOL)didComplete
{ {
[_delegate transitionContext:self didComplete:didComplete]; [_delegate transitionContext:self didComplete:didComplete];