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};
}
}