amis-rpc-design/node_modules/react-native-screens/ios/RNSScreenStackAnimator.mm
2023-10-07 19:42:30 +08:00

321 lines
15 KiB
Plaintext

#import "RNSScreenStackAnimator.h"
#import "RNSScreenStack.h"
#import "RNSScreen.h"
// proportions to default transition duration
static const float RNSSlideOpenTransitionDurationProportion = 1;
static const float RNSFadeOpenTransitionDurationProportion = 0.2 / 0.35;
static const float RNSSlideCloseTransitionDurationProportion = 0.25 / 0.35;
static const float RNSFadeCloseTransitionDurationProportion = 0.15 / 0.35;
static const float RNSFadeCloseDelayTransitionDurationProportion = 0.1 / 0.35;
@implementation RNSScreenStackAnimator {
UINavigationControllerOperation _operation;
NSTimeInterval _transitionDuration;
}
- (instancetype)initWithOperation:(UINavigationControllerOperation)operation
{
if (self = [super init]) {
_operation = operation;
_transitionDuration = 0.35; // default duration in seconds
}
return self;
}
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
RNSScreenView *screen;
if (_operation == UINavigationControllerOperationPush) {
UIViewController *toViewController =
[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
screen = ((RNSScreen *)toViewController).screenView;
} else if (_operation == UINavigationControllerOperationPop) {
UIViewController *fromViewController =
[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
screen = ((RNSScreen *)fromViewController).screenView;
}
if (screen != nil && screen.stackAnimation == RNSScreenStackAnimationNone) {
return 0;
}
if (screen != nil && screen.transitionDuration != nil && [screen.transitionDuration floatValue] >= 0) {
float durationInSeconds = [screen.transitionDuration floatValue] / 1000.0;
return durationInSeconds;
}
return _transitionDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromViewController =
[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
RNSScreenView *screen;
if (_operation == UINavigationControllerOperationPush) {
screen = ((RNSScreen *)toViewController).screenView;
} else if (_operation == UINavigationControllerOperationPop) {
screen = ((RNSScreen *)fromViewController).screenView;
}
if (screen != nil) {
if (screen.fullScreenSwipeEnabled && transitionContext.isInteractive) {
// we are swiping with full width gesture
if (screen.customAnimationOnSwipe) {
[self animateTransitionWithStackAnimation:screen.stackAnimation
transitionContext:transitionContext
toVC:toViewController
fromVC:fromViewController];
} else {
// we have to provide an animation when swiping, otherwise the screen will be popped immediately,
// so in case of no custom animation on swipe set, we provide the one closest to the default
[self animateSimplePushWithTransitionContext:transitionContext toVC:toViewController fromVC:fromViewController];
}
} else {
// we are going forward or provided custom animation on swipe or clicked native header back button
[self animateTransitionWithStackAnimation:screen.stackAnimation
transitionContext:transitionContext
toVC:toViewController
fromVC:fromViewController];
}
}
}
- (void)animateSimplePushWithTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext
toVC:(UIViewController *)toViewController
fromVC:(UIViewController *)fromViewController
{
float containerWidth = transitionContext.containerView.bounds.size.width;
float belowViewWidth = containerWidth * 0.3;
CGAffineTransform rightTransform = CGAffineTransformMakeTranslation(containerWidth, 0);
CGAffineTransform leftTransform = CGAffineTransformMakeTranslation(-belowViewWidth, 0);
if (toViewController.navigationController.view.semanticContentAttribute ==
UISemanticContentAttributeForceRightToLeft) {
rightTransform = CGAffineTransformMakeTranslation(-containerWidth, 0);
leftTransform = CGAffineTransformMakeTranslation(belowViewWidth, 0);
}
if (_operation == UINavigationControllerOperationPush) {
toViewController.view.transform = rightTransform;
[[transitionContext containerView] addSubview:toViewController.view];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
fromViewController.view.transform = leftTransform;
toViewController.view.transform = CGAffineTransformIdentity;
}
completion:^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
toViewController.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
} else if (_operation == UINavigationControllerOperationPop) {
toViewController.view.transform = leftTransform;
[[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
void (^animationBlock)(void) = ^{
toViewController.view.transform = CGAffineTransformIdentity;
fromViewController.view.transform = rightTransform;
};
void (^completionBlock)(BOOL) = ^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
toViewController.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
};
if (!transitionContext.isInteractive) {
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:animationBlock
completion:completionBlock];
} else {
// we don't want the EaseInOut option when swiping to dismiss the view, it is the same in default animation option
[UIView animateWithDuration:[self transitionDuration:transitionContext]
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:animationBlock
completion:completionBlock];
}
}
}
- (void)animateFadeWithTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext
toVC:(UIViewController *)toViewController
fromVC:(UIViewController *)fromViewController
{
toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
if (_operation == UINavigationControllerOperationPush) {
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha = 0.0;
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
toViewController.view.alpha = 1.0;
}
completion:^(BOOL finished) {
toViewController.view.alpha = 1.0;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
} else if (_operation == UINavigationControllerOperationPop) {
[[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
fromViewController.view.alpha = 0.0;
}
completion:^(BOOL finished) {
fromViewController.view.alpha = 1.0;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
}
- (void)animateSlideFromBottomWithTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext
toVC:(UIViewController *)toViewController
fromVC:(UIViewController *)fromViewController
{
CGAffineTransform topBottomTransform =
CGAffineTransformMakeTranslation(0, transitionContext.containerView.bounds.size.height);
if (_operation == UINavigationControllerOperationPush) {
toViewController.view.transform = topBottomTransform;
[[transitionContext containerView] addSubview:toViewController.view];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
fromViewController.view.transform = CGAffineTransformIdentity;
toViewController.view.transform = CGAffineTransformIdentity;
}
completion:^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
toViewController.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
} else if (_operation == UINavigationControllerOperationPop) {
toViewController.view.transform = CGAffineTransformIdentity;
[[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
void (^animationBlock)(void) = ^{
toViewController.view.transform = CGAffineTransformIdentity;
fromViewController.view.transform = topBottomTransform;
};
void (^completionBlock)(BOOL) = ^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
toViewController.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
};
if (!transitionContext.isInteractive) {
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:animationBlock
completion:completionBlock];
} else {
// we don't want the EaseInOut option when swiping to dismiss the view, it is the same in default animation option
[UIView animateWithDuration:[self transitionDuration:transitionContext]
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:animationBlock
completion:completionBlock];
}
}
}
- (void)animateFadeFromBottomWithTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext
toVC:(UIViewController *)toViewController
fromVC:(UIViewController *)fromViewController
{
CGAffineTransform topBottomTransform =
CGAffineTransformMakeTranslation(0, 0.08 * transitionContext.containerView.bounds.size.height);
const float transitionDuration = [self transitionDuration:transitionContext];
if (_operation == UINavigationControllerOperationPush) {
toViewController.view.transform = topBottomTransform;
toViewController.view.alpha = 0.0;
[[transitionContext containerView] addSubview:toViewController.view];
// Android Nougat open animation
// http://aosp.opersys.com/xref/android-7.1.2_r37/xref/frameworks/base/core/res/res/anim/activity_open_enter.xml
[UIView animateWithDuration:transitionDuration * RNSSlideOpenTransitionDurationProportion // defaults to 0.35 s
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
fromViewController.view.transform = CGAffineTransformIdentity;
toViewController.view.transform = CGAffineTransformIdentity;
}
completion:^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
[UIView animateWithDuration:transitionDuration * RNSFadeOpenTransitionDurationProportion // defaults to 0.2 s
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
toViewController.view.alpha = 1.0;
}
completion:nil];
} else if (_operation == UINavigationControllerOperationPop) {
toViewController.view.transform = CGAffineTransformIdentity;
[[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
// Android Nougat exit animation
// http://aosp.opersys.com/xref/android-7.1.2_r37/xref/frameworks/base/core/res/res/anim/activity_close_exit.xml
[UIView animateWithDuration:transitionDuration * RNSSlideCloseTransitionDurationProportion // defaults to 0.25 s
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
toViewController.view.transform = CGAffineTransformIdentity;
fromViewController.view.transform = topBottomTransform;
}
completion:^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
toViewController.view.transform = CGAffineTransformIdentity;
fromViewController.view.alpha = 1.0;
toViewController.view.alpha = 1.0;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
[UIView animateWithDuration:transitionDuration * RNSFadeCloseTransitionDurationProportion // defaults to 0.15 s
delay:transitionDuration * RNSFadeCloseDelayTransitionDurationProportion // defaults to 0.1 s
options:UIViewAnimationOptionCurveLinear
animations:^{
fromViewController.view.alpha = 0.0;
}
completion:nil];
}
}
+ (BOOL)isCustomAnimation:(RNSScreenStackAnimation)animation
{
return (animation != RNSScreenStackAnimationFlip && animation != RNSScreenStackAnimationDefault);
}
- (void)animateTransitionWithStackAnimation:(RNSScreenStackAnimation)animation
transitionContext:(id<UIViewControllerContextTransitioning>)transitionContext
toVC:(UIViewController *)toVC
fromVC:(UIViewController *)fromVC
{
if (animation == RNSScreenStackAnimationSimplePush) {
[self animateSimplePushWithTransitionContext:transitionContext toVC:toVC fromVC:fromVC];
return;
} else if (animation == RNSScreenStackAnimationFade || animation == RNSScreenStackAnimationNone) {
[self animateFadeWithTransitionContext:transitionContext toVC:toVC fromVC:fromVC];
return;
} else if (animation == RNSScreenStackAnimationSlideFromBottom) {
[self animateSlideFromBottomWithTransitionContext:transitionContext toVC:toVC fromVC:fromVC];
return;
} else if (animation == RNSScreenStackAnimationFadeFromBottom) {
[self animateFadeFromBottomWithTransitionContext:transitionContext toVC:toVC fromVC:fromVC];
return;
}
// simple_push is the default custom animation
[self animateSimplePushWithTransitionContext:transitionContext toVC:toVC fromVC:fromVC];
}
@end