diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 6fa2606571..34da2492a9 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -460,6 +460,10 @@ CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; + DB55C2611C6408D6004EDCF5 /* _ASTransitionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */; }; + DB55C2631C6408D6004EDCF5 /* _ASTransitionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */; }; + DB55C2661C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; DBC452DB1C5BF64600B16017 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; }; DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; @@ -781,6 +785,9 @@ D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; + DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = _ASTransitionContext.h; path = ../_ASTransitionContext.h; sourceTree = ""; }; + DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = _ASTransitionContext.m; path = ../_ASTransitionContext.m; sourceTree = ""; }; + DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASContextTransitioning.h; sourceTree = ""; }; DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Diffing.h"; sourceTree = ""; }; DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Diffing.m"; sourceTree = ""; }; DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArrayDiffingTests.m; sourceTree = ""; }; @@ -959,6 +966,7 @@ ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */, ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */, 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */, + DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */, 058D09E1195D050800B7D73C /* Details */, 058D0A01195D050800B7D73C /* Private */, AC6456051B0A333200CF11B8 /* Layout */, @@ -1102,6 +1110,8 @@ 058D0A01195D050800B7D73C /* Private */ = { isa = PBXGroup; children = ( + DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */, + DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */, AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */, AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.m */, 9C65A7291BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h */, @@ -1343,6 +1353,7 @@ DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */, 1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */, 257754A81BEE44CD00737CA5 /* ASTextKitContext.h in Headers */, + DB55C2611C6408D6004EDCF5 /* _ASTransitionContext.h in Headers */, 464052221A3F83C40061C0BA /* ASFlowLayoutController.h in Headers */, 257754AF1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h in Headers */, 058D0A57195D05DC00B7D73C /* ASHighlightOverlayLayer.h in Headers */, @@ -1352,6 +1363,7 @@ 430E7C8F1B4C23F100697A4C /* ASIndexPath.h in Headers */, ACF6ED221B17843500DA7C62 /* ASInsetLayoutSpec.h in Headers */, ACF6ED4B1B17847A00DA7C62 /* ASInternalHelpers.h in Headers */, + DB55C2661C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */, ACF6ED241B17843500DA7C62 /* ASLayout.h in Headers */, 251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */, ACF6ED2A1B17843500DA7C62 /* ASLayoutable.h in Headers */, @@ -1501,6 +1513,7 @@ 34EFC76E1B701CF400AD841F /* ASRatioLayoutSpec.h in Headers */, 34EFC7651B701CCC00AD841F /* ASRelativeSize.h in Headers */, 254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */, + DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */, 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */, B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */, 254C6B751BF94DF4003EC431 /* ASTextKitHelpers.h in Headers */, @@ -1775,6 +1788,7 @@ ACF6ED231B17843500DA7C62 /* ASInsetLayoutSpec.mm in Sources */, ACF6ED4C1B17847A00DA7C62 /* ASInternalHelpers.mm in Sources */, ACF6ED251B17843500DA7C62 /* ASLayout.mm in Sources */, + DB55C2631C6408D6004EDCF5 /* _ASTransitionContext.m in Sources */, 9C5FA3531B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */, 9C5FA35F1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */, 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */, diff --git a/AsyncDisplayKit/ASContextTransitioning.h b/AsyncDisplayKit/ASContextTransitioning.h new file mode 100644 index 0000000000..0eb5d42bae --- /dev/null +++ b/AsyncDisplayKit/ASContextTransitioning.h @@ -0,0 +1,33 @@ +// +// ASContextTransitioning.h +// AsyncDisplayKit +// +// Created by Levi McCallum on 2/4/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@protocol ASContextTransitioning + +/** + @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. + */ +- (CGRect)initialFrameForNode:(ASDisplayNode *)node; + +/** + @abstract The frame for the given node when the transition completes. + @discussion Returns CGRectNull if the node is no longer in the hierarchy after the transition. + */ +- (CGRect)finalFrameForNode:(ASDisplayNode *)node; + +- (NSArray *)sublayouts; + +/** + @abstract Invoke this method when the transition is completed in `transitionLayout:` + @discussion Passing NO to `didComplete` will set the original layout as the new layout. + */ +- (void)completeTransition:(BOOL)didComplete; + +@end diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index 699b6213ed..f9195853d5 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -14,6 +14,7 @@ #import @class ASLayoutSpec; +@protocol ASContextTransitioning; NS_ASSUME_NONNULL_BEGIN @@ -155,6 +156,24 @@ NS_ASSUME_NONNULL_BEGIN - (void)invalidateCalculatedLayout; +/** @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; + +/** + @discussion A place to clean up your nodes after the transition + */ +- (void)didCompleteTransitionLayout:(id)context; + + /** @name Drawing */ diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index ecb35b5a66..773ac7d7bf 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -571,6 +571,17 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface ASDisplayNode (Transitioning) + +/** + @abstract Invalidates the current layout and begins a relayout of the node to the new layout returned in `calculateLayoutThatFits:`. + + @discussion Animation is optional, but will still proceed through the `transitionLayout` methods with `isAnimated == NO`. + */ +- (void)transitionLayoutWithAnimation:(BOOL)animated; + +@end + /** * Convenience methods for debugging. diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 0ce0bd9f6e..2e80e73be0 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -84,7 +84,7 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS #define TIME_SCOPED(outVar) #endif -@interface ASDisplayNode () <_ASDisplayLayerDelegate> +@interface ASDisplayNode () <_ASDisplayLayerDelegate, _ASTransitionContextDelegate> @property (assign, nonatomic) BOOL implicitNodeHierarchyManagement; @@ -996,6 +996,119 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo return CGRectApplyAffineTransform(rect, flattenedTransform); } +#pragma mark - Layout Transition + +- (void)transitionLayoutWithAnimation:(BOOL)animated +{ + [self transitionLayoutToFit:_constrainedSize animated:animated]; +} + +- (void)transitionLayoutToFit:(ASSizeRange)constrainedSize animated:(BOOL)animated +{ + [self invalidateCalculatedLayout]; + [self measureWithSizeRange:constrainedSize]; // Generate a new layout + [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]; +} + +- (void)willTransitionLayout:(id)context +{ +} + +- (void)transitionLayout:(id)context +{ + for (ASLayout *subnodeLayout in [context sublayouts]) { + ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [self _adjustedFrameForLayout:subnodeLayout]; + } + + [context completeTransition:YES]; +} + +- (void)didCompleteTransitionLayout:(id)context +{ + if ([_deletedSubnodes count]) { + for (_ASDisplayNodePosition *position in _deletedSubnodes) { + [self _implicitlyRemoveSubnode:position.node atIndex:position.index]; + } + _deletedSubnodes = @[]; + } +} + +- (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 +{ + [self didCompleteTransitionLayout:context]; + _transitionContext = nil; +} + +- (CGRect)transitionContext:(_ASTransitionContext *)context initialFrameForNode:(ASDisplayNode *)node +{ + for (ASDisplayNode *subnode in _subnodes) { + if (ASObjectIsEqual(node, subnode)) { + return node.frame; + } + } + return CGRectNull; +} + +- (CGRect)transitionContext:(_ASTransitionContext *)context finalFrameForNode:(ASDisplayNode *)node +{ + for (ASLayout *layout in _layout.sublayouts) { + if (ASObjectIsEqual(node, layout.layoutableObject)) { + return [self _adjustedFrameForLayout:layout]; + } + } + return CGRectNull; +} + +- (NSArray *)sublayoutsForTransitioningContext:(_ASTransitionContext *)context +{ + return _layout.sublayouts; +} + #pragma mark - _ASDisplayLayerDelegate - (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer @@ -2053,77 +2166,27 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) if (!_flags.isMeasured) { return; } - - // Assume that _layout was flattened and is 1-level deep. - ASDisplayNode *subnode = nil; - CGRect subnodeFrame = CGRectZero; - for (ASLayout *subnodeLayout in _layout.sublayouts) { - if (![[self class] usesImplicitHierarchyManagement]) { - ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Sublayouts must only contain subnodes' layout. self = %@, subnodes = %@", self, _subnodes); - } - CGPoint adjustedOrigin = subnodeLayout.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 = subnodeLayout.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; - - subnode = ((ASDisplayNode *)subnodeLayout.layoutableObject); - [subnode setFrame:subnodeFrame]; - } if ([[self class] usesImplicitHierarchyManagement]) { - for (_ASDisplayNodePosition *position in _deletedSubnodes) { - [self _implicitlyRemoveSubnode:position.node atIndex:position.index]; - } - - for (_ASDisplayNodePosition *position in _insertedSubnodes) { - [self _implicitlyInsertSubnode:position.node atIndex:position.index]; + [self __transitionLayoutWithAnimation:NO]; + } else { + // Assume that _layout was flattened and is 1-level deep. + CGRect subnodeFrame = CGRectZero; + for (ASLayout *subnodeLayout in _layout.sublayouts) { + ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Sublayouts must only contain subnodes' layout. self = %@, subnodes = %@", self, _subnodes); + subnodeFrame = [self _adjustedFrameForLayout:subnodeLayout]; + ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = subnodeFrame; } } } - (void)_implicitlyInsertSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx { - ASDisplayNodeAssertThreadAffinity(self); - - if (!_managedSubnodes) { - _managedSubnodes = [NSMutableArray array]; - } - - ASDisplayNodeAssert(idx <= [_managedSubnodes count], @"index needs to be in range of the current managed subnodes"); - if (idx == [_managedSubnodes count]) { - [_managedSubnodes addObject:node]; - } else { - [_managedSubnodes insertObject:node atIndex:idx]; - } - [self addSubnode:node]; + [self insertSubnode:node atIndex:idx]; } - (void)_implicitlyRemoveSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx { - ASDisplayNodeAssertThreadAffinity(self); - - if (!_managedSubnodes) { - _managedSubnodes = [NSMutableArray array]; - } - - [_managedSubnodes removeObjectAtIndex:idx]; [node removeFromSupernode]; } diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 2ad55f9080..ca3dd3774f 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -68,3 +68,4 @@ #import #import #import +#import diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 668a5fb512..38e86b5731 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -17,6 +17,7 @@ #import "ASSentinel.h" #import "ASThread.h" #import "ASLayoutOptions.h" +#import "_ASTransitionContext.h" @protocol _ASDisplayLayerDelegate; @class _ASDisplayLayer; @@ -63,10 +64,8 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo ASSizeRange _constrainedSize; UIEdgeInsets _hitTestSlop; NSMutableArray *_subnodes; - - // Subnodes implicitly managed by layout changes - NSMutableArray *_managedSubnodes; + _ASTransitionContext *_transitionContext; NSArray<_ASDisplayNodePosition *> *_insertedSubnodes; NSArray<_ASDisplayNodePosition *> *_deletedSubnodes; @@ -149,6 +148,11 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo - (void)__layout; - (void)__setSupernode:(ASDisplayNode *)supernode; +/** + Clamps the layout's origin or position to 0 if any of the calculated values are infinite. + */ +- (CGRect)_adjustedFrameForLayout:(ASLayout *)layout; + // Private API for helper functions / unit tests. Use ASDisplayNodeDisableHierarchyNotifications() to control this. - (BOOL)__visibilityNotificationsDisabled; - (BOOL)__selfOrParentHasVisibilityNotificationsDisabled; diff --git a/AsyncDisplayKit/_ASTransitionContext.h b/AsyncDisplayKit/_ASTransitionContext.h new file mode 100644 index 0000000000..2c964a99c5 --- /dev/null +++ b/AsyncDisplayKit/_ASTransitionContext.h @@ -0,0 +1,32 @@ +// +// _ASTransitionContext.h +// AsyncDisplayKit +// +// Created by Levi McCallum on 2/4/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +#import "ASContextTransitioning.h" + +@class ASLayout; +@class _ASTransitionContext; + +@protocol _ASTransitionContextDelegate + +- (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete; +- (CGRect)transitionContext:(_ASTransitionContext *)context initialFrameForNode:(ASDisplayNode *)node; +- (CGRect)transitionContext:(_ASTransitionContext *)context finalFrameForNode:(ASDisplayNode *)node; + +- (NSArray *)sublayoutsForTransitioningContext:(_ASTransitionContext *)context; + +@end + +@interface _ASTransitionContext : NSObject + +@property (assign, readonly, nonatomic, getter=isAnimated) BOOL animated; + +- (instancetype)initWithAnimation:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate; + +@end diff --git a/AsyncDisplayKit/_ASTransitionContext.m b/AsyncDisplayKit/_ASTransitionContext.m new file mode 100644 index 0000000000..e5cf012d26 --- /dev/null +++ b/AsyncDisplayKit/_ASTransitionContext.m @@ -0,0 +1,49 @@ +// +// _ASTransitionContext.m +// AsyncDisplayKit +// +// Created by Levi McCallum on 2/4/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "_ASTransitionContext.h" + +@interface _ASTransitionContext () + +@property (weak, nonatomic) id<_ASTransitionContextDelegate> delegate; + +@end + +@implementation _ASTransitionContext + +- (instancetype)initWithAnimation:(BOOL)animated delegate:(id<_ASTransitionContextDelegate>)delegate +{ + self = [super init]; + if (self) { + _animated = animated; + _delegate = delegate; + } + return self; +} + +- (CGRect)initialFrameForNode:(ASDisplayNode *)node +{ + return [_delegate transitionContext:self initialFrameForNode:node]; +} + +- (CGRect)finalFrameForNode:(ASDisplayNode *)node +{ + return [_delegate transitionContext:self finalFrameForNode:node]; +} + +- (NSArray *)sublayouts +{ + return [_delegate sublayoutsForTransitioningContext:self]; +} + +- (void)completeTransition:(BOOL)didComplete +{ + [_delegate transitionContext:self didComplete:didComplete]; +} + +@end