From 4e1c12db9f9f56814120646451a467028d9691bb Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Wed, 30 Oct 2024 14:30:33 +0100 Subject: [PATCH 01/18] Use 0.5 for default transition duration value __but__ keep old animation proportions --- ios/RNSScreenStackAnimator.mm | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index abb2cf69fd..7d09b727ca 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -4,11 +4,19 @@ #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; +static constexpr NSTimeInterval RNSReferenceTransitionDurationForProportionInSeconds = 0.35; +static constexpr NSTimeInterval RNSDefaultTransitionDuration = 0.5; + +static constexpr float RNSSlideOpenTransitionDurationProportion = 1; +static constexpr float RNSFadeOpenTransitionDurationProportion = + 0.2 / RNSReferenceTransitionDurationForProportionInSeconds; +static constexpr float RNSSlideCloseTransitionDurationProportion = + 0.25 / RNSReferenceTransitionDurationForProportionInSeconds; +static constexpr float RNSFadeCloseTransitionDurationProportion = + 0.15 / RNSReferenceTransitionDurationForProportionInSeconds; +static constexpr float RNSFadeCloseDelayTransitionDurationProportion = + 0.1 / RNSReferenceTransitionDurationForProportionInSeconds; + // same value is used in other projects using similar approach for transistions // and it looks the most similar to the value used by Apple static constexpr float RNSShadowViewMaxAlpha = 0.1; @@ -22,7 +30,7 @@ - (instancetype)initWithOperation:(UINavigationControllerOperation)operation { if (self = [super init]) { _operation = operation; - _transitionDuration = 0.35; // default duration in seconds + _transitionDuration = RNSDefaultTransitionDuration; // default duration in seconds } return self; } From cc641c669d9ed3432a763f4d7ee589d3b4939f3d Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Wed, 30 Oct 2024 14:58:52 +0100 Subject: [PATCH 02/18] Write Push transition using property animator and spring timing curve --- ios/RNSScreenStackAnimator.mm | 44 +++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index 7d09b727ca..0ee3695800 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -135,23 +135,33 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled [[transitionContext containerView] insertSubview:shadowView belowSubview:toViewController.view]; shadowView.alpha = 0.0; } - - [UIView animateWithDuration:[self transitionDuration:transitionContext] - animations:^{ - fromViewController.view.transform = leftTransform; - toViewController.view.transform = CGAffineTransformIdentity; - if (shadowView) { - shadowView.alpha = RNSShadowViewMaxAlpha; - } - } - completion:^(BOOL finished) { - if (shadowView) { - [shadowView removeFromSuperview]; - } - fromViewController.view.transform = CGAffineTransformIdentity; - toViewController.view.transform = CGAffineTransformIdentity; - [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; - }]; + + // Default curve provider is as defined below, however spring timing defined this way + // ignores the requested duration of the animation, effectively impairing our `animationDuration` prop. + // Damping of 1.0 seems close enough and we keep `animationDuration` functional. + // id timingCurveProvider = [[UISpringTimingParameters alloc] init]; + + id timingCurveProvider = [[UISpringTimingParameters alloc] initWithDampingRatio:1.0]; + UIViewPropertyAnimator *animator = [[UIViewPropertyAnimator alloc] initWithDuration:[self transitionDuration:transitionContext] timingParameters:timingCurveProvider]; + + [animator addAnimations:^{ + fromViewController.view.transform = leftTransform; + toViewController.view.transform = CGAffineTransformIdentity; + if (shadowView) { + shadowView.alpha = RNSShadowViewMaxAlpha; + } + }]; + + [animator addCompletion:^(UIViewAnimatingPosition finalPosition) { + if (shadowView) { + [shadowView removeFromSuperview]; + } + fromViewController.view.transform = CGAffineTransformIdentity; + toViewController.view.transform = CGAffineTransformIdentity; + [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; + }]; + + [animator startAnimation]; } else if (_operation == UINavigationControllerOperationPop) { toViewController.view.transform = leftTransform; [[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view]; From 845ab1c214ba7bf3559ec2cb5c3a837b414c677d Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Wed, 30 Oct 2024 14:59:18 +0100 Subject: [PATCH 03/18] Linter --- ios/RNSScreenStackAnimator.mm | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index 0ee3695800..69e8f58464 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -135,15 +135,17 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled [[transitionContext containerView] insertSubview:shadowView belowSubview:toViewController.view]; shadowView.alpha = 0.0; } - + // Default curve provider is as defined below, however spring timing defined this way // ignores the requested duration of the animation, effectively impairing our `animationDuration` prop. // Damping of 1.0 seems close enough and we keep `animationDuration` functional. // id timingCurveProvider = [[UISpringTimingParameters alloc] init]; - + id timingCurveProvider = [[UISpringTimingParameters alloc] initWithDampingRatio:1.0]; - UIViewPropertyAnimator *animator = [[UIViewPropertyAnimator alloc] initWithDuration:[self transitionDuration:transitionContext] timingParameters:timingCurveProvider]; - + UIViewPropertyAnimator *animator = + [[UIViewPropertyAnimator alloc] initWithDuration:[self transitionDuration:transitionContext] + timingParameters:timingCurveProvider]; + [animator addAnimations:^{ fromViewController.view.transform = leftTransform; toViewController.view.transform = CGAffineTransformIdentity; @@ -151,7 +153,7 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled shadowView.alpha = RNSShadowViewMaxAlpha; } }]; - + [animator addCompletion:^(UIViewAnimatingPosition finalPosition) { if (shadowView) { [shadowView removeFromSuperview]; @@ -160,7 +162,7 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled toViewController.view.transform = CGAffineTransformIdentity; [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; - + [animator startAnimation]; } else if (_operation == UINavigationControllerOperationPop) { toViewController.view.transform = leftTransform; From d5d580983961dd22b2d152d3b05669b3b410719e Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Wed, 30 Oct 2024 15:04:47 +0100 Subject: [PATCH 04/18] Write Pop transition using property animator and spring timing curve --- ios/RNSScreenStackAnimator.mm | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index 69e8f58464..3a25214d79 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -179,7 +179,7 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled shadowView.alpha = 0.0; } }; - void (^completionBlock)(BOOL) = ^(BOOL finished) { + void (^completionBlock)(UIViewAnimatingPosition) = ^(UIViewAnimatingPosition finalPosition) { if (shadowView) { [shadowView removeFromSuperview]; } @@ -189,16 +189,24 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled }; if (!transitionContext.isInteractive) { - [UIView animateWithDuration:[self transitionDuration:transitionContext] - animations:animationBlock - completion:completionBlock]; + id timingCurveProvider = [[UISpringTimingParameters alloc] initWithDampingRatio:1.0]; + + UIViewPropertyAnimator *animator = + [[UIViewPropertyAnimator alloc] initWithDuration:[self transitionDuration:transitionContext] + timingParameters:timingCurveProvider]; + + [animator addAnimations:animationBlock]; + [animator addCompletion:completionBlock]; + [animator startAnimation]; } 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]; + UIViewPropertyAnimator *animator = + [[UIViewPropertyAnimator alloc] initWithDuration:[self transitionDuration:transitionContext] + curve:UIViewAnimationCurveLinear + animations:animationBlock]; + + [animator addCompletion:completionBlock]; + [animator startAnimation]; } } } From 24fc134d5fca7bfaa5484ce5473cc045791cd1b5 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Fri, 1 Nov 2024 10:42:10 +0100 Subject: [PATCH 05/18] Implement non-interactive simple push using UIViewPropertyAnimator [PRB] --- ios/RNSScreenStackAnimator.h | 2 +- ios/RNSScreenStackAnimator.mm | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/ios/RNSScreenStackAnimator.h b/ios/RNSScreenStackAnimator.h index f96eae7756..7165cef2ec 100644 --- a/ios/RNSScreenStackAnimator.h +++ b/ios/RNSScreenStackAnimator.h @@ -2,7 +2,7 @@ @interface RNSScreenStackAnimator : NSObject -- (instancetype)initWithOperation:(UINavigationControllerOperation)operation; +- (nonnull instancetype)initWithOperation:(UINavigationControllerOperation)operation; + (BOOL)isCustomAnimation:(RNSScreenStackAnimation)animation; @end diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index 3a25214d79..1235a294ec 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -179,7 +179,8 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled shadowView.alpha = 0.0; } }; - void (^completionBlock)(UIViewAnimatingPosition) = ^(UIViewAnimatingPosition finalPosition) { + + void (^completionBlockImpl)(void) = ^{ if (shadowView) { [shadowView removeFromSuperview]; } @@ -188,6 +189,14 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }; + void (^completionBlock)(UIViewAnimatingPosition) = ^(UIViewAnimatingPosition finalPosition) { + completionBlockImpl(); + }; + + void (^completionBlock2)(BOOL) = ^(BOOL finalPosition) { + completionBlockImpl(); + }; + if (!transitionContext.isInteractive) { id timingCurveProvider = [[UISpringTimingParameters alloc] initWithDampingRatio:1.0]; @@ -200,13 +209,11 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled [animator startAnimation]; } else { // we don't want the EaseInOut option when swiping to dismiss the view, it is the same in default animation option - UIViewPropertyAnimator *animator = - [[UIViewPropertyAnimator alloc] initWithDuration:[self transitionDuration:transitionContext] - curve:UIViewAnimationCurveLinear - animations:animationBlock]; - - [animator addCompletion:completionBlock]; - [animator startAnimation]; + [UIView animateWithDuration:[self transitionDuration:transitionContext] + delay:0.0 + options:UIViewAnimationOptionCurveLinear + animations:animationBlock + completion:completionBlock2]; } } } From ebe507ef52402f7d724a3e1df3572910b5e68d51 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Fri, 1 Nov 2024 16:33:41 +0100 Subject: [PATCH 06/18] Implement interactive simple-push using UIViewPropertyAnimator --- ios/RNSPercentDrivenInteractiveTransition.h | 12 +++ ios/RNSPercentDrivenInteractiveTransition.mm | 69 +++++++++++++++++ ios/RNSScreenStack.mm | 13 +++- ios/RNSScreenStackAnimator.h | 4 + ios/RNSScreenStackAnimator.mm | 80 ++++++++++++-------- 5 files changed, 144 insertions(+), 34 deletions(-) create mode 100644 ios/RNSPercentDrivenInteractiveTransition.h create mode 100644 ios/RNSPercentDrivenInteractiveTransition.mm diff --git a/ios/RNSPercentDrivenInteractiveTransition.h b/ios/RNSPercentDrivenInteractiveTransition.h new file mode 100644 index 0000000000..e47401e450 --- /dev/null +++ b/ios/RNSPercentDrivenInteractiveTransition.h @@ -0,0 +1,12 @@ +#import +#import "RNSScreenStackAnimator.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface RNSPercentDrivenInteractiveTransition : UIPercentDrivenInteractiveTransition + +@property (nonatomic, nullable) RNSScreenStackAnimator *animationController; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/RNSPercentDrivenInteractiveTransition.mm b/ios/RNSPercentDrivenInteractiveTransition.mm new file mode 100644 index 0000000000..68f188ec86 --- /dev/null +++ b/ios/RNSPercentDrivenInteractiveTransition.mm @@ -0,0 +1,69 @@ +#import "RNSPercentDrivenInteractiveTransition.h" + +@implementation RNSPercentDrivenInteractiveTransition { + RNSScreenStackAnimator *_animationController; +} + +#pragma mark - UIViewControllerInteractiveTransitioning + +- (void)startInteractiveTransition:(id)transitionContext +{ + [super startInteractiveTransition:transitionContext]; +} + +#pragma mark - UIPercentDrivenInteractiveTransition + +// `updateInteractiveTransition`, `finishInteractiveTransition`, +// `cancelInteractiveTransition` are forwared by superclass to +// corresponding methods in transition context. In case +// of "classical CA driven animaitons", such as UIView animation blocks +// or direct utilization of CoreAnimation API, context drives the animation, +// however in case of UIViewPropertyAnimator it does not. We need +// to drive animation manually and this is exactly what happens below. + +- (void)updateInteractiveTransition:(CGFloat)percentComplete +{ + if (_animationController != nil) { + [self.animationController.inFlightAnimator setFractionComplete:percentComplete]; + } + [super updateInteractiveTransition:percentComplete]; +} + +- (void)finishInteractiveTransition +{ + [self finalizeInteractiveTransitionWithAnimationWasCancelled:NO]; + [super finishInteractiveTransition]; +} + +- (void)cancelInteractiveTransition +{ + [self finalizeInteractiveTransitionWithAnimationWasCancelled:YES]; + [super cancelInteractiveTransition]; +} + +#pragma mark - Helpers + +- (void)finalizeInteractiveTransitionWithAnimationWasCancelled:(BOOL)cancelled +{ + if (_animationController == nil) { + return; + } + + UIViewPropertyAnimator *_Nullable animator = self.animationController.inFlightAnimator; + if (animator == nil) { + return; + } + + BOOL shouldReverseAnimation = cancelled; + + id timingParams = [[UISpringTimingParameters alloc] initWithDampingRatio:1.0]; + + [animator pauseAnimation]; + [animator setReversed:shouldReverseAnimation]; + [animator continueAnimationWithTimingParameters:timingParams durationFactor:(1 - animator.fractionComplete)]; + + // System retains it & we don't need it anymore. + _animationController = nil; +} + +@end diff --git a/ios/RNSScreenStack.mm b/ios/RNSScreenStack.mm index cc26600522..0d0d6c4d38 100644 --- a/ios/RNSScreenStack.mm +++ b/ios/RNSScreenStack.mm @@ -20,6 +20,7 @@ #import "RCTTouchHandler+RNSUtility.h" #endif // RCT_NEW_ARCH_ENABLED +#import "RNSPercentDrivenInteractiveTransition.h" #import "RNSScreen.h" #import "RNSScreenStack.h" #import "RNSScreenStackAnimator.h" @@ -149,7 +150,7 @@ @implementation RNSScreenStackView { NSMutableArray *_reactSubviews; BOOL _invalidated; BOOL _isFullWidthSwiping; - UIPercentDrivenInteractiveTransition *_interactionController; + RNSPercentDrivenInteractiveTransition *_interactionController; __weak RNSScreenStackManager *_manager; BOOL _updateScheduled; #ifdef RCT_NEW_ARCH_ENABLED @@ -869,7 +870,7 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer switch (gestureRecognizer.state) { case UIGestureRecognizerStateBegan: { - _interactionController = [UIPercentDrivenInteractiveTransition new]; + _interactionController = [RNSPercentDrivenInteractiveTransition new]; [_controller popViewControllerAnimated:YES]; break; } @@ -916,7 +917,7 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer if (_interactionController == nil && fromView.reactSuperview) { BOOL shouldCancelDismiss = [self shouldCancelDismissFromView:fromView toView:toView]; if (shouldCancelDismiss) { - _interactionController = [UIPercentDrivenInteractiveTransition new]; + _interactionController = [RNSPercentDrivenInteractiveTransition new]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self->_interactionController cancelInteractiveTransition]; self->_interactionController = nil; @@ -929,6 +930,10 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer }); } } + + if (_interactionController != nil) { + [_interactionController setAnimationController:animationController]; + } return _interactionController; } @@ -1111,7 +1116,7 @@ - (void)startScreenTransition { if (_interactionController == nil) { _customAnimation = YES; - _interactionController = [UIPercentDrivenInteractiveTransition new]; + _interactionController = [RNSPercentDrivenInteractiveTransition new]; [_controller popViewControllerAnimated:YES]; } } diff --git a/ios/RNSScreenStackAnimator.h b/ios/RNSScreenStackAnimator.h index 7165cef2ec..d101a1b63d 100644 --- a/ios/RNSScreenStackAnimator.h +++ b/ios/RNSScreenStackAnimator.h @@ -2,6 +2,10 @@ @interface RNSScreenStackAnimator : NSObject +/// This property is filled only when there is an property aniamator associated with an ongoing interactive transition. +/// In our case this is when we're handling full swipe or edge back gesture. +@property (nonatomic, strong, nullable, readonly) UIViewPropertyAnimator *inFlightAnimator; + - (nonnull instancetype)initWithOperation:(UINavigationControllerOperation)operation; + (BOOL)isCustomAnimation:(RNSScreenStackAnimation)animation; diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index 1235a294ec..74c61004d7 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -3,27 +3,34 @@ #import "RNSScreen.h" -// proportions to default transition duration -static constexpr NSTimeInterval RNSReferenceTransitionDurationForProportionInSeconds = 0.35; +#pragma mark - Constants + +// Default duration for transitions in seconds. Note, that this enforces the default +// only on Paper. On Fabric the transition duration coming from JS layer +// is never null, thus it defaults to the value set in component codegen spec. static constexpr NSTimeInterval RNSDefaultTransitionDuration = 0.5; +// Proportions for diffrent phases of more complex animations. +// The reference duration differs from default transition duration, +// because we've changed the default duration & we want to keep proportions +// in tact. Unit = seconds. +static constexpr NSTimeInterval RNSTransitionDurationForProportion = 0.35; + static constexpr float RNSSlideOpenTransitionDurationProportion = 1; -static constexpr float RNSFadeOpenTransitionDurationProportion = - 0.2 / RNSReferenceTransitionDurationForProportionInSeconds; -static constexpr float RNSSlideCloseTransitionDurationProportion = - 0.25 / RNSReferenceTransitionDurationForProportionInSeconds; -static constexpr float RNSFadeCloseTransitionDurationProportion = - 0.15 / RNSReferenceTransitionDurationForProportionInSeconds; -static constexpr float RNSFadeCloseDelayTransitionDurationProportion = - 0.1 / RNSReferenceTransitionDurationForProportionInSeconds; - -// same value is used in other projects using similar approach for transistions +static constexpr float RNSFadeOpenTransitionDurationProportion = 0.2 / RNSTransitionDurationForProportion; +static constexpr float RNSSlideCloseTransitionDurationProportion = 0.25 / RNSTransitionDurationForProportion; +static constexpr float RNSFadeCloseTransitionDurationProportion = 0.15 / RNSTransitionDurationForProportion; +static constexpr float RNSFadeCloseDelayTransitionDurationProportion = 0.1 / RNSTransitionDurationForProportion; + +// Value used for dimming view attached for tranistion time. +// Same value is used in other projects using similar approach for transistions // and it looks the most similar to the value used by Apple static constexpr float RNSShadowViewMaxAlpha = 0.1; @implementation RNSScreenStackAnimator { UINavigationControllerOperation _operation; NSTimeInterval _transitionDuration; + UIViewPropertyAnimator *_Nullable _inFlightAnimator; } - (instancetype)initWithOperation:(UINavigationControllerOperation)operation @@ -31,10 +38,13 @@ - (instancetype)initWithOperation:(UINavigationControllerOperation)operation if (self = [super init]) { _operation = operation; _transitionDuration = RNSDefaultTransitionDuration; // default duration in seconds + _inFlightAnimator = nil; } return self; } +#pragma mark - UIViewControllerAnimatedTransitioning + - (NSTimeInterval)transitionDuration:(id)transitionContext { RNSScreenView *screen; @@ -105,6 +115,19 @@ - (void)animateTransition:(id)transitionCo } } +- (id)interruptibleAnimatorForTransition: + (id)transitionContext +{ + return _inFlightAnimator; +} + +- (void)animationEnded:(BOOL)transitionCompleted +{ + _inFlightAnimator = nil; +} + +#pragma mark - Animation implementations + - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled transitionContext:(id)transitionContext toVC:(UIViewController *)toViewController @@ -180,7 +203,7 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled } }; - void (^completionBlockImpl)(void) = ^{ + void (^completionBlock)(UIViewAnimatingPosition) = ^(UIViewAnimatingPosition finalPosition) { if (shadowView) { [shadowView removeFromSuperview]; } @@ -189,14 +212,6 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }; - void (^completionBlock)(UIViewAnimatingPosition) = ^(UIViewAnimatingPosition finalPosition) { - completionBlockImpl(); - }; - - void (^completionBlock2)(BOOL) = ^(BOOL finalPosition) { - completionBlockImpl(); - }; - if (!transitionContext.isInteractive) { id timingCurveProvider = [[UISpringTimingParameters alloc] initWithDampingRatio:1.0]; @@ -209,11 +224,14 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled [animator startAnimation]; } 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:completionBlock2]; + UIViewPropertyAnimator *animator = + [[UIViewPropertyAnimator alloc] initWithDuration:[self transitionDuration:transitionContext] + curve:UIViewAnimationCurveLinear + animations:animationBlock]; + + [animator addCompletion:completionBlock]; + [animator setUserInteractionEnabled:YES]; + _inFlightAnimator = animator; } } } @@ -446,10 +464,7 @@ - (void)animateWithNoAnimation:(id)transit } } -+ (BOOL)isCustomAnimation:(RNSScreenStackAnimation)animation -{ - return (animation != RNSScreenStackAnimationFlip && animation != RNSScreenStackAnimationDefault); -} +#pragma mark - Helpers - (void)animateTransitionWithStackAnimation:(RNSScreenStackAnimation)animation shadowEnabled:(BOOL)shadowEnabled @@ -477,4 +492,9 @@ - (void)animateTransitionWithStackAnimation:(RNSScreenStackAnimation)animation [self animateSimplePushWithShadowEnabled:shadowEnabled transitionContext:transitionContext toVC:toVC fromVC:fromVC]; } ++ (BOOL)isCustomAnimation:(RNSScreenStackAnimation)animation +{ + return (animation != RNSScreenStackAnimationFlip && animation != RNSScreenStackAnimationDefault); +} + @end From aeec2382d14424ab3c5776037fdf8979dc76f4fe Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Fri, 1 Nov 2024 17:09:01 +0100 Subject: [PATCH 07/18] Make RNSScreenStackAnimator a source of truth on animation properties [PRB] --- ios/RNSPercentDrivenInteractiveTransition.mm | 2 +- ios/RNSScreenStackAnimator.h | 9 +++++++ ios/RNSScreenStackAnimator.mm | 27 +++++++++++++------- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/ios/RNSPercentDrivenInteractiveTransition.mm b/ios/RNSPercentDrivenInteractiveTransition.mm index 68f188ec86..5869723476 100644 --- a/ios/RNSPercentDrivenInteractiveTransition.mm +++ b/ios/RNSPercentDrivenInteractiveTransition.mm @@ -56,7 +56,7 @@ - (void)finalizeInteractiveTransitionWithAnimationWasCancelled:(BOOL)cancelled BOOL shouldReverseAnimation = cancelled; - id timingParams = [[UISpringTimingParameters alloc] initWithDampingRatio:1.0]; + id timingParams = [_animationController timingParamsForAnimationCompletion]; [animator pauseAnimation]; [animator setReversed:shouldReverseAnimation]; diff --git a/ios/RNSScreenStackAnimator.h b/ios/RNSScreenStackAnimator.h index d101a1b63d..fc26e63c5e 100644 --- a/ios/RNSScreenStackAnimator.h +++ b/ios/RNSScreenStackAnimator.h @@ -7,6 +7,15 @@ @property (nonatomic, strong, nullable, readonly) UIViewPropertyAnimator *inFlightAnimator; - (nonnull instancetype)initWithOperation:(UINavigationControllerOperation)operation; + +/// In case of interactive / interruptible transition (e.g. swipe back gesture) this method should return +/// timing parameters expected by animator to be used for animation completion (e.g. when user's +/// gesture had ended). +/// +/// @return timing curve provider expected to be used for animation completion or nil, +/// when there is no interactive transition running. +- (nullable id)timingParamsForAnimationCompletion; + + (BOOL)isCustomAnimation:(RNSScreenStackAnimation)animation; @end diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index 74c61004d7..4f21d1f449 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -164,10 +164,9 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled // Damping of 1.0 seems close enough and we keep `animationDuration` functional. // id timingCurveProvider = [[UISpringTimingParameters alloc] init]; - id timingCurveProvider = [[UISpringTimingParameters alloc] initWithDampingRatio:1.0]; UIViewPropertyAnimator *animator = [[UIViewPropertyAnimator alloc] initWithDuration:[self transitionDuration:transitionContext] - timingParameters:timingCurveProvider]; + timingParameters:[RNSScreenStackAnimator defaultSpringTimingParametersApprox]]; [animator addAnimations:^{ fromViewController.view.transform = leftTransform; @@ -213,11 +212,9 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled }; if (!transitionContext.isInteractive) { - id timingCurveProvider = [[UISpringTimingParameters alloc] initWithDampingRatio:1.0]; - - UIViewPropertyAnimator *animator = - [[UIViewPropertyAnimator alloc] initWithDuration:[self transitionDuration:transitionContext] - timingParameters:timingCurveProvider]; + UIViewPropertyAnimator *animator = [[UIViewPropertyAnimator alloc] + initWithDuration:[self transitionDuration:transitionContext] + timingParameters:[RNSScreenStackAnimator defaultSpringTimingParametersApprox]]; [animator addAnimations:animationBlock]; [animator addCompletion:completionBlock]; @@ -464,6 +461,18 @@ - (void)animateWithNoAnimation:(id)transit } } +#pragma mark - Public API + +- (nullable id)timingParamsForAnimationCompletion +{ + return [RNSScreenStackAnimator defaultSpringTimingParametersApprox]; +} + ++ (BOOL)isCustomAnimation:(RNSScreenStackAnimation)animation +{ + return (animation != RNSScreenStackAnimationFlip && animation != RNSScreenStackAnimationDefault); +} + #pragma mark - Helpers - (void)animateTransitionWithStackAnimation:(RNSScreenStackAnimation)animation @@ -492,9 +501,9 @@ - (void)animateTransitionWithStackAnimation:(RNSScreenStackAnimation)animation [self animateSimplePushWithShadowEnabled:shadowEnabled transitionContext:transitionContext toVC:toVC fromVC:fromVC]; } -+ (BOOL)isCustomAnimation:(RNSScreenStackAnimation)animation ++ (UISpringTimingParameters *)defaultSpringTimingParametersApprox { - return (animation != RNSScreenStackAnimationFlip && animation != RNSScreenStackAnimationDefault); + return [[UISpringTimingParameters alloc] initWithDampingRatio:1.0]; } @end From 12b747db6fdfaf0cb33c5e5b5ae238666ab245aa Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Fri, 1 Nov 2024 20:32:10 +0100 Subject: [PATCH 08/18] Update documentation: default transition lasts 500ms --- guides/GUIDE_FOR_LIBRARY_AUTHORS.md | 2 +- native-stack/README.md | 2 +- src/fabric/ModalScreenNativeComponent.ts | 2 +- src/fabric/ScreenNativeComponent.ts | 2 +- src/native-stack/types.tsx | 2 +- src/types.tsx | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md index d4635f0283..530e612ce5 100644 --- a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md +++ b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md @@ -297,7 +297,7 @@ When using `vertical` option, options `fullScreenSwipeEnabled: true`, `customAni ### `transitionDuration` (iOS only) -Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. Defaults to `350`. +Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. Defaults to `500`. The duration of `default` and `flip` transitions isn't customizable. diff --git a/native-stack/README.md b/native-stack/README.md index b8ab002b25..69c918d665 100644 --- a/native-stack/README.md +++ b/native-stack/README.md @@ -366,7 +366,7 @@ A string that can be used as a fallback for `headerTitle`. #### `transitionDuration` (iOS only) -Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. Defaults to `350`. +Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. Defaults to `500`. The duration of `default` and `flip` transitions isn't customizable. diff --git a/src/fabric/ModalScreenNativeComponent.ts b/src/fabric/ModalScreenNativeComponent.ts index 16cd62b5e4..3eceead40b 100644 --- a/src/fabric/ModalScreenNativeComponent.ts +++ b/src/fabric/ModalScreenNativeComponent.ts @@ -99,7 +99,7 @@ export interface NativeProps extends ViewProps { gestureResponseDistance?: GestureResponseDistanceType; stackPresentation?: WithDefault; stackAnimation?: WithDefault; - transitionDuration?: WithDefault; + transitionDuration?: WithDefault; replaceAnimation?: WithDefault; swipeDirection?: WithDefault; hideKeyboardOnSwipe?: boolean; diff --git a/src/fabric/ScreenNativeComponent.ts b/src/fabric/ScreenNativeComponent.ts index 1eb9071ac3..bc0631e556 100644 --- a/src/fabric/ScreenNativeComponent.ts +++ b/src/fabric/ScreenNativeComponent.ts @@ -99,7 +99,7 @@ export interface NativeProps extends ViewProps { gestureResponseDistance?: GestureResponseDistanceType; stackPresentation?: WithDefault; stackAnimation?: WithDefault; - transitionDuration?: WithDefault; + transitionDuration?: WithDefault; replaceAnimation?: WithDefault; swipeDirection?: WithDefault; hideKeyboardOnSwipe?: boolean; diff --git a/src/native-stack/types.tsx b/src/native-stack/types.tsx index f8e279d523..c97e4a56b7 100644 --- a/src/native-stack/types.tsx +++ b/src/native-stack/types.tsx @@ -520,7 +520,7 @@ export type NativeStackNavigationOptions = { */ title?: string; /** - * Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. Defaults to `350`. + * Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. Defaults to `500`. * The duration of `default` and `flip` transitions isn't customizable. * * @platform ios diff --git a/src/types.tsx b/src/types.tsx index 2af4400d75..ed906e5dc7 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -456,7 +456,7 @@ export interface ScreenProps extends ViewProps { */ swipeDirection?: SwipeDirectionTypes; /** - * Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. Defaults to `350`. + * Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. Defaults to `500`. * The duration of `default` and `flip` transitions isn't customizable. * * @platform ios From 9bde620790f18b37077cdde2bb4895c5afa1b97b Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Fri, 1 Nov 2024 20:33:11 +0100 Subject: [PATCH 09/18] Update default transition duration in Android spec --- .../facebook/react/viewmanagers/RNSScreenManagerDelegate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java index d73de5dda3..c3e9bd18ae 100644 --- a/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java @@ -91,7 +91,7 @@ public void setProperty(T view, String propName, @Nullable Object value) { mViewManager.setStackAnimation(view, (String) value); break; case "transitionDuration": - mViewManager.setTransitionDuration(view, value == null ? 350 : ((Double) value).intValue()); + mViewManager.setTransitionDuration(view, value == null ? 500 : ((Double) value).intValue()); break; case "replaceAnimation": mViewManager.setReplaceAnimation(view, (String) value); From 0e1f818f9db32217a5ae275e2e1880517842a9ef Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Fri, 1 Nov 2024 21:43:59 +0100 Subject: [PATCH 10/18] Implement both interactive and not slide_from_left using UIViewPropertyAnimator --- ios/RNSScreenStackAnimator.mm | 50 ++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index 4f21d1f449..e19e66ccfd 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -252,16 +252,22 @@ - (void)animateSlideFromLeftWithTransitionContext:(id Date: Mon, 4 Nov 2024 15:43:26 +0100 Subject: [PATCH 11/18] Move comment on default spring animation to helper function --- ios/RNSScreenStackAnimator.mm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index e19e66ccfd..30e2a27fe1 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -159,11 +159,6 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled shadowView.alpha = 0.0; } - // Default curve provider is as defined below, however spring timing defined this way - // ignores the requested duration of the animation, effectively impairing our `animationDuration` prop. - // Damping of 1.0 seems close enough and we keep `animationDuration` functional. - // id timingCurveProvider = [[UISpringTimingParameters alloc] init]; - UIViewPropertyAnimator *animator = [[UIViewPropertyAnimator alloc] initWithDuration:[self transitionDuration:transitionContext] timingParameters:[RNSScreenStackAnimator defaultSpringTimingParametersApprox]]; @@ -515,6 +510,11 @@ - (void)animateTransitionWithStackAnimation:(RNSScreenStackAnimation)animation + (UISpringTimingParameters *)defaultSpringTimingParametersApprox { + // Default curve provider is as defined below, however spring timing defined this way + // ignores the requested duration of the animation, effectively impairing our `animationDuration` prop. + // Damping of 1.0 seems close enough and we keep `animationDuration` functional. + // id timingCurveProvider = [[UISpringTimingParameters alloc] init]; + return [[UISpringTimingParameters alloc] initWithDampingRatio:1.0]; } From 23300c6c16e0e925f33ba512514f3fa6227a7df5 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Tue, 5 Nov 2024 12:39:40 +0100 Subject: [PATCH 12/18] stash changes for another animation --- ios/RNSScreenStackAnimator.mm | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index 30e2a27fe1..119d4a7640 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -370,11 +370,21 @@ - (void)animateSlideFromBottomWithTransitionContext:(id Date: Wed, 6 Nov 2024 09:56:10 +0100 Subject: [PATCH 13/18] Set damping ratio to 4.56 --- ios/RNSScreenStackAnimator.mm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index 119d4a7640..c12e88047b 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -525,7 +525,13 @@ + (UISpringTimingParameters *)defaultSpringTimingParametersApprox // Damping of 1.0 seems close enough and we keep `animationDuration` functional. // id timingCurveProvider = [[UISpringTimingParameters alloc] init]; - return [[UISpringTimingParameters alloc] initWithDampingRatio:1.0]; + // According to "Programming iOS 14" by Matt Neuburg, the params for the default spring are as follows: + // mass = 3, stiffness = 1000, damping = 500. Damping ratio is computed using formula + // ratio = damping / (2 * sqrt(stiffness * mass)) ==> default damping should be ~= 4,56. + // I've found afterwards that this is even indeicated here: + // https://developer.apple.com/documentation/uikit/uispringtimingparameters/1649802-init?language=objc + + return [[UISpringTimingParameters alloc] initWithDampingRatio:4.56]; } @end From bbad9e3a6d3a543b70976fa8bab4ace461e447ef Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Wed, 6 Nov 2024 09:58:45 +0100 Subject: [PATCH 14/18] Restore slide_from_bottom to old behaviour --- ios/RNSScreenStackAnimator.mm | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index c12e88047b..f39a492b9a 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -370,21 +370,11 @@ - (void)animateSlideFromBottomWithTransitionContext:(id Date: Wed, 6 Nov 2024 12:13:49 +0100 Subject: [PATCH 15/18] Fix typos --- ios/RNSScreenStackAnimator.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index f39a492b9a..49ceda97e1 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -512,13 +512,13 @@ + (UISpringTimingParameters *)defaultSpringTimingParametersApprox { // Default curve provider is as defined below, however spring timing defined this way // ignores the requested duration of the animation, effectively impairing our `animationDuration` prop. - // Damping of 1.0 seems close enough and we keep `animationDuration` functional. + // We want to keep `animationDuration` functional. // id timingCurveProvider = [[UISpringTimingParameters alloc] init]; // According to "Programming iOS 14" by Matt Neuburg, the params for the default spring are as follows: // mass = 3, stiffness = 1000, damping = 500. Damping ratio is computed using formula - // ratio = damping / (2 * sqrt(stiffness * mass)) ==> default damping should be ~= 4,56. - // I've found afterwards that this is even indeicated here: + // ratio = damping / (2 * sqrt(stiffness * mass)) ==> default damping ratio should be ~= 4,56. + // I've found afterwards that this is even indicated here: // https://developer.apple.com/documentation/uikit/uispringtimingparameters/1649802-init?language=objc return [[UISpringTimingParameters alloc] initWithDampingRatio:4.56]; From 7b0bdca9d3ff1ab21b7439c695092651ccb209b1 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Wed, 6 Nov 2024 15:39:09 +0100 Subject: [PATCH 16/18] Review fixes --- ios/RNSPercentDrivenInteractiveTransition.mm | 6 +++--- ios/RNSScreenStackAnimator.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ios/RNSPercentDrivenInteractiveTransition.mm b/ios/RNSPercentDrivenInteractiveTransition.mm index 5869723476..459aa289d0 100644 --- a/ios/RNSPercentDrivenInteractiveTransition.mm +++ b/ios/RNSPercentDrivenInteractiveTransition.mm @@ -16,7 +16,7 @@ - (void)startInteractiveTransition:(id)tra // `updateInteractiveTransition`, `finishInteractiveTransition`, // `cancelInteractiveTransition` are forwared by superclass to // corresponding methods in transition context. In case -// of "classical CA driven animaitons", such as UIView animation blocks +// of "classical CA driven animations", such as UIView animation blocks // or direct utilization of CoreAnimation API, context drives the animation, // however in case of UIViewPropertyAnimator it does not. We need // to drive animation manually and this is exactly what happens below. @@ -24,7 +24,7 @@ - (void)startInteractiveTransition:(id)tra - (void)updateInteractiveTransition:(CGFloat)percentComplete { if (_animationController != nil) { - [self.animationController.inFlightAnimator setFractionComplete:percentComplete]; + [_animationController.inFlightAnimator setFractionComplete:percentComplete]; } [super updateInteractiveTransition:percentComplete]; } @@ -49,7 +49,7 @@ - (void)finalizeInteractiveTransitionWithAnimationWasCancelled:(BOOL)cancelled return; } - UIViewPropertyAnimator *_Nullable animator = self.animationController.inFlightAnimator; + UIViewPropertyAnimator *_Nullable animator = _animationController.inFlightAnimator; if (animator == nil) { return; } diff --git a/ios/RNSScreenStackAnimator.h b/ios/RNSScreenStackAnimator.h index fc26e63c5e..4100a2d8db 100644 --- a/ios/RNSScreenStackAnimator.h +++ b/ios/RNSScreenStackAnimator.h @@ -3,7 +3,7 @@ @interface RNSScreenStackAnimator : NSObject /// This property is filled only when there is an property aniamator associated with an ongoing interactive transition. -/// In our case this is when we're handling full swipe or edge back gesture. +/// In our case this is when we're handling full screen swipe or edge back gesture. @property (nonatomic, strong, nullable, readonly) UIViewPropertyAnimator *inFlightAnimator; - (nonnull instancetype)initWithOperation:(UINavigationControllerOperation)operation; From 7a2dfc948eebc056700e9281d496cfc215a0a174 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Wed, 6 Nov 2024 16:41:18 +0100 Subject: [PATCH 17/18] Add fix for missing header animation --- ios/RNSScreenStackAnimator.mm | 191 +++++++++++++++++++--------------- 1 file changed, 106 insertions(+), 85 deletions(-) diff --git a/ios/RNSScreenStackAnimator.mm b/ios/RNSScreenStackAnimator.mm index 49ceda97e1..410a461285 100644 --- a/ios/RNSScreenStackAnimator.mm +++ b/ios/RNSScreenStackAnimator.mm @@ -70,6 +70,12 @@ - (NSTimeInterval)transitionDuration:(id)t return _transitionDuration; } +- (id)interruptibleAnimatorForTransition: + (id)transitionContext +{ + return _inFlightAnimator; +} + - (void)animateTransition:(id)transitionContext { UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; @@ -115,12 +121,6 @@ - (void)animateTransition:(id)transitionCo } } -- (id)interruptibleAnimatorForTransition: - (id)transitionContext -{ - return _inFlightAnimator; -} - - (void)animationEnded:(BOOL)transitionCompleted { _inFlightAnimator = nil; @@ -179,7 +179,7 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled toViewController.view.transform = CGAffineTransformIdentity; [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; - + _inFlightAnimator = animator; [animator startAnimation]; } else if (_operation == UINavigationControllerOperationPop) { toViewController.view.transform = leftTransform; @@ -213,6 +213,7 @@ - (void)animateSimplePushWithShadowEnabled:(BOOL)shadowEnabled [animator addAnimations:animationBlock]; [animator addCompletion:completionBlock]; + _inFlightAnimator = animator; [animator startAnimation]; } else { // we don't want the EaseInOut option when swiping to dismiss the view, it is the same in default animation option @@ -261,7 +262,7 @@ - (void)animateSlideFromLeftWithTransitionContext:(id Date: Wed, 6 Nov 2024 16:56:36 +0100 Subject: [PATCH 18/18] Update comment for inFlightAnimator --- ios/RNSScreenStackAnimator.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ios/RNSScreenStackAnimator.h b/ios/RNSScreenStackAnimator.h index 4100a2d8db..d9c4c67ec9 100644 --- a/ios/RNSScreenStackAnimator.h +++ b/ios/RNSScreenStackAnimator.h @@ -2,8 +2,7 @@ @interface RNSScreenStackAnimator : NSObject -/// This property is filled only when there is an property aniamator associated with an ongoing interactive transition. -/// In our case this is when we're handling full screen swipe or edge back gesture. +/// This property is filled whenever there is an ongoing animation and cleared on animation end. @property (nonatomic, strong, nullable, readonly) UIViewPropertyAnimator *inFlightAnimator; - (nonnull instancetype)initWithOperation:(UINavigationControllerOperation)operation;