From 178d94dbf76e52ff046f8e89c9b2d821d2eda9d6 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Mon, 16 Dec 2024 13:28:15 +0100 Subject: [PATCH] fix(iOS): restore behaviour of RNSScreenStackAnimationNone (#2565) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description There is still some header animation noticeable for some reason. <-- This is because we use `fade` transition with duration 0 and do not override interruptible animator! To prevent the animation we could either return `nil` interruptible animator (but overriding the method comes with it's own set of problems, see #2563 and other related) or handle the `none` animation much earlier, when calling `showViewControllers:animated:` in `updateContainer` (pass `animated: NO`). PS: If we would want to pass `animated: NO` I wonder what would happen to dismiss prevention - we implemented it at the stage of the animation start... We need to think this through. Note: Must be implemented with old animation API, because `UIViewPropertyAnimator` does not allow for 0 duration (it uses default if the specified animation duration is below some undocumented treshold). This regression was introduced with #2477 ## Changes We now use old API for `animation: none` & still rely on fade animation to implement it. Note the points made above ☝🏻 - we should refactor this code to make advantage of `animated:` parameter of the `showViewControllers:animated:`. ## Test code and steps to reproduce `TestAnimation` - set stack presentation to `none` - it works as prior to v4. WIP VIDEO ## Checklist - [ ] Included code example that can be used to test this change - [ ] Updated TS types - [ ] Updated documentation: - [ ] https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md - [ ] https://github.com/software-mansion/react-native-screens/blob/main/native-stack/README.md - [ ] https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx - [ ] https://github.com/software-mansion/react-native-screens/blob/main/src/native-stack/types.tsx - [ ] Ensured that CI passes --- apps/src/tests/TestAnimation.tsx | 9 +++- ios/RNSScreenStackAnimator.h | 2 + ios/RNSScreenStackAnimator.mm | 85 +++++++++++++++++++++++--------- 3 files changed, 70 insertions(+), 26 deletions(-) diff --git a/apps/src/tests/TestAnimation.tsx b/apps/src/tests/TestAnimation.tsx index e4058c5df7..21595ae3c0 100644 --- a/apps/src/tests/TestAnimation.tsx +++ b/apps/src/tests/TestAnimation.tsx @@ -77,13 +77,18 @@ function Fifth({ navigation }: RoutePropBase<'Fifth'>): React.ReactNode { ); } +function HeaderRight() { + return ( + + ); +} + export default function App() { return ( diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index 65a99f2841..89caf8d00d 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -59,7 +59,7 @@ - (NSTimeInterval)transitionDuration:(id)t } if (screen != nil && screen.stackAnimation == RNSScreenStackAnimationNone) { - return 0; + return 0.0; } if (screen != nil && screen.transitionDuration != nil && [screen.transitionDuration floatValue] >= 0) { @@ -70,12 +70,6 @@ - (NSTimeInterval)transitionDuration:(id)t return _transitionDuration; } -//- (id)interruptibleAnimatorForTransition: -// (id)transitionContext -//{ -// return _inFlightAnimator; -//} - - (void)animateTransition:(id)transitionContext { UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; @@ -489,6 +483,38 @@ - (void)animateWithNoAnimation:(id)transit } } +- (void)animateNoneWithTransitionContext:(id)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]]; + }]; + } +} + #pragma mark - Public API - (nullable id)timingParamsForAnimationCompletion @@ -509,24 +535,35 @@ - (void)animateTransitionWithStackAnimation:(RNSScreenStackAnimation)animation toVC:(UIViewController *)toVC fromVC:(UIViewController *)fromVC { - if (animation == RNSScreenStackAnimationSimplePush) { - [self animateSimplePushWithShadowEnabled:shadowEnabled transitionContext:transitionContext toVC:toVC fromVC:fromVC]; - return; - } else if (animation == RNSScreenStackAnimationSlideFromLeft) { - [self animateSlideFromLeftWithTransitionContext: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; + switch (animation) { + case RNSScreenStackAnimationSimplePush: + [self animateSimplePushWithShadowEnabled:shadowEnabled + transitionContext:transitionContext + toVC:toVC + fromVC:fromVC]; + return; + case RNSScreenStackAnimationSlideFromLeft: + [self animateSlideFromLeftWithTransitionContext:transitionContext toVC:toVC fromVC:fromVC]; + return; + case RNSScreenStackAnimationFade: + [self animateFadeWithTransitionContext:transitionContext toVC:toVC fromVC:fromVC]; + return; + case RNSScreenStackAnimationSlideFromBottom: + [self animateSlideFromBottomWithTransitionContext:transitionContext toVC:toVC fromVC:fromVC]; + return; + case RNSScreenStackAnimationFadeFromBottom: + [self animateFadeFromBottomWithTransitionContext:transitionContext toVC:toVC fromVC:fromVC]; + return; + case RNSScreenStackAnimationNone: + [self animateNoneWithTransitionContext:transitionContext toVC:toVC fromVC:fromVC]; + return; + default: + // simple_push is the default custom animation + [self animateSimplePushWithShadowEnabled:shadowEnabled + transitionContext:transitionContext + toVC:toVC + fromVC:fromVC]; } - // simple_push is the default custom animation - [self animateSimplePushWithShadowEnabled:shadowEnabled transitionContext:transitionContext toVC:toVC fromVC:fromVC]; } + (UISpringTimingParameters *)defaultSpringTimingParametersApprox