Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(iOS)!: change default animation curve & duration #2477

Merged
merged 19 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion guides/GUIDE_FOR_LIBRARY_AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
12 changes: 12 additions & 0 deletions ios/RNSPercentDrivenInteractiveTransition.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#import <UIKit/UIKit.h>
#import "RNSScreenStackAnimator.h"

NS_ASSUME_NONNULL_BEGIN

@interface RNSPercentDrivenInteractiveTransition : UIPercentDrivenInteractiveTransition

@property (nonatomic, nullable) RNSScreenStackAnimator *animationController;

@end

NS_ASSUME_NONNULL_END
69 changes: 69 additions & 0 deletions ios/RNSPercentDrivenInteractiveTransition.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#import "RNSPercentDrivenInteractiveTransition.h"

@implementation RNSPercentDrivenInteractiveTransition {
RNSScreenStackAnimator *_animationController;
}

#pragma mark - UIViewControllerInteractiveTransitioning

- (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)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
kkafar marked this conversation as resolved.
Show resolved Hide resolved
// 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];
kkafar marked this conversation as resolved.
Show resolved Hide resolved
}
[super updateInteractiveTransition:percentComplete];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why header looks so bad in those transitions, I think this line should control the state of animation of header too. Are those values not changing pretty linearly ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Header looks bad only in non-interactive transitions. TBH I'm surprised that it does not look bad in old implementation - the docs suggest that it should look bad

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know what code drives the header transition in non-interactive transitions (notice that I'm not swiping on these recordings). Need to spend some time on this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs mention the native header transition where the back button title travels to the place of title when navigating back/forward. It works only with the default animations. For simple_push it was the fade animation in header iirc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you're right. I've just confirmed that it has not been a fluke - on main there is nice fade transition. Do you happen to remember where this is animated? Or is this handled entirely by UIKit?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Figured this out in: 7a2dfc9

}

- (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<UITimingCurveProvider> timingParams = [_animationController timingParamsForAnimationCompletion];

[animator pauseAnimation];
[animator setReversed:shouldReverseAnimation];
[animator continueAnimationWithTimingParameters:timingParams durationFactor:(1 - animator.fractionComplete)];

// System retains it & we don't need it anymore.
_animationController = nil;
}

@end
13 changes: 9 additions & 4 deletions ios/RNSScreenStack.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -149,7 +150,7 @@ @implementation RNSScreenStackView {
NSMutableArray<RNSScreenView *> *_reactSubviews;
BOOL _invalidated;
BOOL _isFullWidthSwiping;
UIPercentDrivenInteractiveTransition *_interactionController;
RNSPercentDrivenInteractiveTransition *_interactionController;
__weak RNSScreenStackManager *_manager;
BOOL _updateScheduled;
#ifdef RCT_NEW_ARCH_ENABLED
Expand Down Expand Up @@ -869,7 +870,7 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer

switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan: {
_interactionController = [UIPercentDrivenInteractiveTransition new];
_interactionController = [RNSPercentDrivenInteractiveTransition new];
[_controller popViewControllerAnimated:YES];
break;
}
Expand Down Expand Up @@ -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;
Expand All @@ -929,6 +930,10 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer
});
}
}

if (_interactionController != nil) {
kkafar marked this conversation as resolved.
Show resolved Hide resolved
[_interactionController setAnimationController:animationController];
}
return _interactionController;
}

Expand Down Expand Up @@ -1111,7 +1116,7 @@ - (void)startScreenTransition
{
if (_interactionController == nil) {
_customAnimation = YES;
_interactionController = [UIPercentDrivenInteractiveTransition new];
_interactionController = [RNSPercentDrivenInteractiveTransition new];
[_controller popViewControllerAnimated:YES];
}
}
Expand Down
15 changes: 14 additions & 1 deletion ios/RNSScreenStackAnimator.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,20 @@

@interface RNSScreenStackAnimator : NSObject <UIViewControllerAnimatedTransitioning>

- (instancetype)initWithOperation:(UINavigationControllerOperation)operation;
/// 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also why 3 /?

Suggested change
/// 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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a doc-style comment in objective-c (XCode?), same as /** */ Idk your keybindings, but when using vim plugin, in normal mode you can press shift + k to display the docs for given symbol. I'm sure w/o Vim there is some other key combination to trigger this.

@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<UITimingCurveProvider>)timingParamsForAnimationCompletion;

+ (BOOL)isCustomAnimation:(RNSScreenStackAnimation)animation;

@end
Loading
Loading