ComponentKit/Components/CKTransitionComponent.mm (115 lines of code) (raw):

/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #import "CKTransitionComponent.h" #import <ComponentKit/CKCasting.h> #import <ComponentKit/CKComponentInternal.h> #import <ComponentKit/CKComponentSubclass.h> #import <ComponentKit/CKCompositeComponent.h> @interface CKTransitionComponent : CKCompositeComponent + (instancetype)newWithComponent:(CKComponent *)component onInitialMount:(CAAnimation *)animationOnInitialMount onFinalUnmount:(CAAnimation *)animationOnFinalUnmount triggerValue:(id<NSObject>)trigger; @end static auto disappearingPreviousComponentAnimation(CAAnimation *disappearAnimation, CKComponent *previousComponent, CKComponent *newComponent) -> CKComponentAnimation { return CKComponentAnimationHooks{ .willRemount = ^{ auto const childView = [previousComponent viewForAnimation]; RCCAssertWithCategory( childView != nil, [previousComponent className], @"Can't animate component without a view. " "Check %@ is from the previousComponent tree, has a view and is mounted.", [previousComponent className] ); auto const snapshotView = [childView snapshotViewAfterScreenUpdates:NO]; snapshotView.layer.anchorPoint = childView.layer.anchorPoint; snapshotView.userInteractionEnabled = NO; return snapshotView; }, .didRemount = ^UIView *(UIView *snapshotView) { auto const newView = [newComponent viewForAnimation]; RCCAssertWithCategory(newView != nil, [newComponent className], @"Can't animate %@ without a view.", [newComponent className]); if (newView == nil || snapshotView == nil) { return nil; // Avoid crashing in insertSubview:aboveSubview: } auto const frame = CGRect{ newView.frame.origin, snapshotView.bounds.size, }; snapshotView.frame = frame; [newView.superview insertSubview:snapshotView aboveSubview:newView]; [CATransaction begin]; [CATransaction setCompletionBlock:^{ [snapshotView removeFromSuperview]; }]; [snapshotView.layer addAnimation:disappearAnimation forKey:nil]; [CATransaction commit]; return snapshotView; }, .cleanup = ^(UIView *snapshotView) { [snapshotView removeFromSuperview]; }, }; } @implementation CKTransitionComponent { CAAnimation *_animationOnInitialMount; CAAnimation *_animationOnFinalUnmount; id<NSObject> _trigger; } + (instancetype)newWithComponent:(CKComponent *)component onInitialMount:(CAAnimation *)animationOnInitialMount onFinalUnmount:(CAAnimation *)animationOnFinalUnmount triggerValue:(id<NSObject>)trigger { if (component == nil) { return nil; } CKComponentScope s(self); auto const c = component.viewConfiguration.viewClass().hasView() ? [super newWithComponent:component] : [super newWithView:{[UIView class]} component:component]; if (c != nil) { c->_animationOnInitialMount = animationOnInitialMount; c->_animationOnFinalUnmount = animationOnFinalUnmount; c->_trigger = trigger; } return c; } - (std::vector<CKComponentAnimation>)animationsFromPreviousComponent:(CKComponent *)previousComponent { auto const prev = CK::objCForceCast<CKTransitionComponent>(previousComponent); if (RCObjectIsEqual(_trigger, prev->_trigger)) { return {}; } return { {self, _animationOnInitialMount}, disappearingPreviousComponentAnimation(_animationOnFinalUnmount, previousComponent, self) }; } @end namespace CK { template <typename T> static CAAnimation *toCA(const CK::Optional<T>& a) { return a.mapToPtr([](const T& a) { return a.toCA(); }); } auto BuilderDetails::TransitionComponentDetails::factory(CKComponent *component, const Optional<Animation::Initial> &initialAnimation, const Optional<Animation::Final> &finalAnimation, id<NSObject> triggerValue) -> CKComponent * { return [CKTransitionComponent newWithComponent:component onInitialMount:toCA(initialAnimation) onFinalUnmount:toCA(finalAnimation) triggerValue:triggerValue]; } auto TransitionComponentBuilder() -> TransitionComponentBuilderEmpty { return {}; } auto TransitionComponentBuilder(const CK::ComponentSpecContext &c) -> TransitionComponentBuilderContext { return {c}; } }