@@ -642,30 +642,23 @@ - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
642642{
643643 ASDN::MutexLocker l (__instanceLock__);
644644 if (! [self shouldMeasureWithSizeRange: constrainedSize]) {
645- return _layout ;
645+ return _calculatedLayout ;
646646 }
647647
648648 [self cancelLayoutTransitionsInProgress ];
649649
650- ASLayout *previousLayout = _layout ;
650+ ASLayout *previousLayout = _calculatedLayout ;
651651 ASLayout *newLayout = [self calculateLayoutThatFits: constrainedSize];
652652
653- if (ASHierarchyStateIncludesLayoutPending (_hierarchyState)) {
654- _pendingLayoutTransition = [[ASLayoutTransition alloc ] initWithNode: self
655- pendingLayout: newLayout
656- previousLayout: previousLayout];
657- } else {
658- ASLayoutTransition *layoutTransition = nil ;
659- if (self.usesImplicitHierarchyManagement ) {
660- layoutTransition = [[ASLayoutTransition alloc ] initWithNode: self
661- pendingLayout: newLayout
662- previousLayout: previousLayout];
663- }
664-
665- [self _applyLayout: newLayout layoutTransition: layoutTransition];
666- [self _completeLayoutCalculation ];
653+ _pendingLayoutTransition = [[ASLayoutTransition alloc ] initWithNode: self
654+ pendingLayout: newLayout
655+ previousLayout: previousLayout];
656+
657+ if (ASHierarchyStateIncludesLayoutPending (_hierarchyState) == NO ) {
658+ // Complete the pending layout transition immediately
659+ [self _completePendingLayoutTransition ];
667660 }
668-
661+
669662 return newLayout;
670663}
671664
@@ -686,12 +679,12 @@ - (BOOL)shouldMeasureWithSizeRange:(ASSizeRange)constrainedSize
686679 // Only generate a new layout if:
687680 // - The current layout is dirty
688681 // - The passed constrained size is different than the layout's constrained size
689- return ([self _hasDirtyLayout ] || !ASSizeRangeEqualToSizeRange (constrainedSize, _layout .constrainedSizeRange ));
682+ return ([self _hasDirtyLayout ] || !ASSizeRangeEqualToSizeRange (constrainedSize, _calculatedLayout .constrainedSizeRange ));
690683}
691684
692685- (BOOL )_hasDirtyLayout
693686{
694- return _layout == nil || _layout .isDirty ;
687+ return _calculatedLayout == nil || _calculatedLayout .isDirty ;
695688}
696689
697690- (ASLayoutableType)layoutableType
@@ -710,13 +703,14 @@ - (void)transitionLayoutWithAnimation:(BOOL)animated
710703 shouldMeasureAsync : (BOOL )shouldMeasureAsync
711704 measurementCompletion : (void (^)())completion
712705{
713- if (_layout == nil ) {
706+ if (_calculatedLayout == nil ) {
714707 // constrainedSizeRange returns a struct and is invalid to call on nil.
715708 // Defaulting to CGSizeZero can cause negative values in client layout code.
716709 return ;
717710 }
711+
718712 [self invalidateCalculatedLayout ];
719- [self transitionLayoutWithSizeRange: _layout .constrainedSizeRange
713+ [self transitionLayoutWithSizeRange: _calculatedLayout .constrainedSizeRange
720714 animated: animated
721715 shouldMeasureAsync: shouldMeasureAsync
722716 measurementCompletion: completion];
@@ -778,12 +772,11 @@ - (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
778772 return ;
779773 }
780774
781- ASLayout *previousLayout = _layout ;
782- [self _applyLayout : newLayout layoutTransition: nil ];
775+ ASLayout *previousLayout = _calculatedLayout ;
776+ [self setCalculatedLayout : newLayout];
783777
784778 ASDisplayNodePerformBlockOnEverySubnode (self, ^(ASDisplayNode * _Nonnull node) {
785- [node _applyPendingLayoutContext ];
786- [node _completeLayoutCalculation ];
779+ [node _completePendingLayoutTransition ];
787780 node.hierarchyState &= (~ASHierarchyStateLayoutPending);
788781 });
789782
@@ -793,48 +786,26 @@ - (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
793786 completion ();
794787 }
795788
789+ // Setup pending layout transition for animation
796790 _pendingLayoutTransition = [[ASLayoutTransition alloc ] initWithNode: self
797791 pendingLayout: newLayout
798- previousLayout: previousLayout];
792+ previousLayout: previousLayout];
793+ // Setup context for pending layout transition. we need to hold a strong reference to the context
794+ _pendingLayoutTransitionContext = [[_ASTransitionContext alloc ] initWithAnimation: animated
795+ layoutDelegate: _pendingLayoutTransition
796+ completionDelegate: self ];
797+
798+ // Apply the subnode insertion immediately to be able to animate the nodes
799799 [_pendingLayoutTransition applySubnodeInsertions ];
800-
801- _transitionContext = [[_ASTransitionContext alloc ] initWithAnimation: animated
802- layoutDelegate: _pendingLayoutTransition
803- completionDelegate: self ];
804- [self animateLayoutTransition: _transitionContext];
800+
801+ // Kick off animating the layout transition
802+ [self animateLayoutTransition: _pendingLayoutTransitionContext];
805803 });
806804 };
807805
808806 ASPerformBlockOnBackgroundThread (transitionBlock);
809807}
810808
811- - (void )_completeLayoutCalculation
812- {
813- ASDN::MutexLocker l (__instanceLock__);
814- [self calculatedLayoutDidChange ];
815-
816- // We generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go.
817- // This is also because measurement is usually asynchronous, but placeholders need to be set up synchronously.
818- // First measurement is guaranteed to be before the node is onscreen, so we can create the image async. but still have it appear sync.
819- if (_placeholderEnabled && [self _displaysAsynchronously ] && self.contents == nil ) {
820-
821- // Zero-sized nodes do not require a placeholder.
822- CGSize layoutSize = (_layout ? _layout.size : CGSizeZero);
823- if (CGSizeEqualToSize (layoutSize, CGSizeZero)) {
824- return ;
825- }
826-
827- if (!_placeholderImage) {
828- _placeholderImage = [self placeholderImage ];
829- }
830- }
831- }
832-
833- - (void )calculatedLayoutDidChange
834- {
835- // subclass override
836- }
837-
838809- (void )cancelLayoutTransitionsInProgress
839810{
840811 ASDN::MutexLocker l (__instanceLock__);
@@ -888,25 +859,108 @@ - (BOOL)_shouldAbortTransitionWithID:(int32_t)transitionID
888859 return (!_transitionInProgress || _transitionID != transitionID);
889860}
890861
862+ #pragma mark - Layout Transition API / ASDisplayNode (Beta)
863+
864+ /*
865+ * Hook for subclasse to perform an animation based on the given ASContextTransitioning. By default this just layouts
866+ * applies all subnodes without animation and calls completes the transition on the context.
867+ */
891868- (void )animateLayoutTransition : (id <ASContextTransitioning>)context
892869{
893- [self __layoutSubnodes ];
870+ [self __layoutSublayouts ];
894871 [context completeTransition: YES ];
895872}
896873
874+ /*
875+ * Hook for subclasses to clean up nodes after the transition happened. Furthermore this can be used from subclasses
876+ * to manually perform deletions.
877+ */
897878- (void )didCompleteLayoutTransition : (id <ASContextTransitioning>)context
898879{
899880 [_pendingLayoutTransition applySubnodeRemovals ];
900- [self _completeLayoutCalculation ];
901- _pendingLayoutTransition = nil ;
902881}
903882
904883#pragma mark - _ASTransitionContextCompletionDelegate
905884
885+ /*
886+ * After completeTransition: is called on the ASContextTransitioning object in animateLayoutTransition: this
887+ * delegate method will be called that start the completion process of the
888+ */
906889- (void )transitionContext : (_ASTransitionContext *)context didComplete : (BOOL )didComplete
907890{
908891 [self didCompleteLayoutTransition: context];
909- _transitionContext = nil ;
892+ _pendingLayoutTransitionContext = nil ;
893+
894+ [self _pendingLayoutTransitionDidComplete ];
895+ }
896+
897+ #pragma mark - Layout
898+
899+ /*
900+ * Completes the pending layout transition immediately without going through the the Layout Transition Animation API
901+ */
902+ - (void )_completePendingLayoutTransition
903+ {
904+ ASDN::MutexLocker l (__instanceLock__);
905+ if (_pendingLayoutTransition) {
906+ [self setCalculatedLayout: _pendingLayoutTransition.pendingLayout];
907+ [self _completeLayoutTransition: _pendingLayoutTransition];
908+ }
909+ [self _pendingLayoutTransitionDidComplete ];
910+ }
911+
912+ /*
913+ * Can be directly called to commit the given layout transition immediately to complete without calling through to the
914+ * Layout Transition Animation API
915+ */
916+ - (void )_completeLayoutTransition : (ASLayoutTransition *)layoutTransition
917+ {
918+ // Layout transition is not supported for non implicit hierarchy managed nodes yet
919+ if (layoutTransition == nil || self.usesImplicitHierarchyManagement == NO ) {
920+ return ;
921+ }
922+
923+ // Trampoline to the main thread if necessary
924+ if (ASDisplayNodeThreadIsMain () || layoutTransition.isSynchronous == NO ) {
925+ [layoutTransition commitTransition ];
926+ } else {
927+ // Subnode insertions and removals need to happen always on the main thread if at least one subnode is already loaded
928+ ASPerformBlockOnMainThread (^{
929+ [layoutTransition commitTransition ];
930+ });
931+ }
932+ }
933+
934+ - (void )_pendingLayoutTransitionDidComplete
935+ {
936+ ASDN::MutexLocker l (__instanceLock__);
937+
938+ // Subclass hook
939+ [self calculatedLayoutDidChange ];
940+
941+ // We generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go.
942+ // This is also because measurement is usually asynchronous, but placeholders need to be set up synchronously.
943+ // First measurement is guaranteed to be before the node is onscreen, so we can create the image async. but still have it appear sync.
944+ if (_placeholderEnabled && [self _displaysAsynchronously ] && self.contents == nil ) {
945+
946+ // Zero-sized nodes do not require a placeholder.
947+ CGSize layoutSize = (_calculatedLayout ? _calculatedLayout.size : CGSizeZero);
948+ if (CGSizeEqualToSize (layoutSize, CGSizeZero)) {
949+ return ;
950+ }
951+
952+ if (!_placeholderImage) {
953+ _placeholderImage = [self placeholderImage ];
954+ }
955+ }
956+
957+ // Cleanup pending layout transition
958+ _pendingLayoutTransition = nil ;
959+ }
960+
961+ - (void )calculatedLayoutDidChange
962+ {
963+ // subclass override
910964}
911965
912966#pragma mark - Asynchronous display
@@ -1068,7 +1122,7 @@ - (void)__setNeedsLayout
10681122
10691123 __instanceLock__.lock ();
10701124
1071- if (_layout == nil ) {
1125+ if (_calculatedLayout == nil ) {
10721126 // Can't proceed without a layout as no constrained size would be available
10731127 __instanceLock__.unlock ();
10741128 return ;
@@ -1086,11 +1140,11 @@ - (void)__setNeedsLayout
10861140 }
10871141
10881142 // This is the root node. Trigger a full measurement pass on *current* thread. Old constrained size is re-used.
1089- [self measureWithSizeRange: _layout .constrainedSizeRange];
1143+ [self measureWithSizeRange: _calculatedLayout .constrainedSizeRange];
10901144
10911145 CGRect oldBounds = self.bounds ;
10921146 CGSize oldSize = oldBounds.size ;
1093- CGSize newSize = _layout .size ;
1147+ CGSize newSize = _calculatedLayout .size ;
10941148
10951149 if (! CGSizeEqualToSize (oldSize, newSize)) {
10961150 self.bounds = (CGRect){ oldBounds.origin , newSize };
@@ -1168,24 +1222,6 @@ - (void)measureNodeWithBoundsIfNecessary:(CGRect)bounds
11681222 }
11691223}
11701224
1171- - (void )layout
1172- {
1173- ASDisplayNodeAssertMainThread ();
1174-
1175- if ([self _hasDirtyLayout ]) {
1176- return ;
1177- }
1178-
1179- [self __layoutSubnodes ];
1180- }
1181-
1182- - (void )__layoutSubnodes
1183- {
1184- for (ASLayout *subnodeLayout in _layout.sublayouts ) {
1185- ((ASDisplayNode *)subnodeLayout.layoutableObject ).frame = [subnodeLayout frame ];
1186- }
1187- }
1188-
11891225- (void )layoutDidFinish
11901226{
11911227 // Hook for subclasses
@@ -2080,19 +2116,30 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
20802116- (ASLayout *)calculatedLayout
20812117{
20822118 ASDN::MutexLocker l (__instanceLock__);
2083- return _layout;
2119+ return _calculatedLayout;
2120+ }
2121+
2122+ - (void )setCalculatedLayout : (ASLayout *)calculatedLayout
2123+ {
2124+ ASDN::MutexLocker l (__instanceLock__);
2125+
2126+ ASDisplayNodeAssertTrue (calculatedLayout.layoutableObject == self);
2127+ ASDisplayNodeAssertTrue (calculatedLayout.size .width >= 0.0 );
2128+ ASDisplayNodeAssertTrue (calculatedLayout.size .height >= 0.0 );
2129+
2130+ _calculatedLayout = calculatedLayout;
20842131}
20852132
20862133- (CGSize)calculatedSize
20872134{
20882135 ASDN::MutexLocker l (__instanceLock__);
2089- return _layout .size ;
2136+ return _calculatedLayout .size ;
20902137}
20912138
20922139- (ASSizeRange)constrainedSizeForCalculatedLayout
20932140{
20942141 ASDN::MutexLocker l (__instanceLock__);
2095- return _layout .constrainedSizeRange ;
2142+ return _calculatedLayout .constrainedSizeRange ;
20962143}
20972144
20982145- (void )setLayoutSpecBlock : (ASLayoutSpecBlock)layoutSpecBlock
@@ -2149,7 +2196,7 @@ - (void)invalidateCalculatedLayout
21492196
21502197 // This will cause the next call to -measureWithSizeRange: to actually compute a new layout
21512198 // instead of returning the current layout
2152- _layout .dirty = YES ;
2199+ _calculatedLayout .dirty = YES ;
21532200}
21542201
21552202- (void )__didLoad
@@ -2516,7 +2563,7 @@ - (void)_applyPendingLayoutContext
25162563- (void )_applyLayout : (ASLayout *)layout layoutTransition : (ASLayoutTransition *)layoutTransition
25172564{
25182565 ASDN::MutexLocker l (__instanceLock__);
2519- _layout = layout;
2566+ _calculatedLayout = layout;
25202567
25212568 ASDisplayNodeAssertTrue (layout.layoutableObject == self);
25222569 ASDisplayNodeAssertTrue (layout.size .width >= 0.0 );
@@ -2531,15 +2578,35 @@ - (void)_applyLayout:(ASLayout *)layout layoutTransition:(ASLayoutTransition *)l
25312578
25322579 // Subnode insertions and removals need to happen always on the main thread if at least one subnode is already loaded
25332580 ASPerformBlockOnMainThread (^{
2534- [layoutTransition startTransition ];
2581+ [layoutTransition commitTransition ];
25352582 });
25362583
25372584 return ;
25382585 }
25392586
2540- [layoutTransition startTransition ];
2587+ [layoutTransition commitTransition ];
25412588}
25422589
2590+ - (void )layout
2591+ {
2592+ ASDisplayNodeAssertMainThread ();
2593+
2594+ if ([self _hasDirtyLayout ]) {
2595+ return ;
2596+ }
2597+
2598+ [self __layoutSublayouts ];
2599+ }
2600+
2601+ - (void )__layoutSublayouts
2602+ {
2603+ for (ASLayout *subnodeLayout in _calculatedLayout.sublayouts ) {
2604+ ((ASDisplayNode *)subnodeLayout.layoutableObject ).frame = [subnodeLayout frame ];
2605+ }
2606+ }
2607+
2608+ #pragma mark - Display
2609+
25432610- (void )displayWillStart
25442611{
25452612 ASDisplayNodeAssertMainThread ();
0 commit comments