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: add transition duration on iOS #1259

Merged
merged 10 commits into from
Feb 7, 2022
1 change: 1 addition & 0 deletions TestsExample/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import Test1213 from './src/Test1213';
import Test1214 from './src/Test1214';
import Test1227 from './src/Test1227';
import Test1228 from './src/Test1228';
import Test1259 from './src/Test1259';

enableFreeze(true);

Expand Down
50 changes: 50 additions & 0 deletions TestsExample/src/Test1259.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as React from 'react';
import {Button} from 'react-native';
import {NavigationContainer, ParamListBase} from '@react-navigation/native';
import {
createNativeStackNavigator,
NativeStackNavigationProp,
} from 'react-native-screens/native-stack';

const Stack = createNativeStackNavigator();

export default function App() {
return (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
transitionDuration: 2000,
stackAnimation: 'slide_from_bottom',
}}>
<Stack.Screen name="First" component={First} />
<Stack.Screen name="Second" component={Second} />
</Stack.Navigator>
</NavigationContainer>
);
}

function First({
navigation,
}: {
navigation: NativeStackNavigationProp<ParamListBase>;
}) {
return (
<Button
title="Tap me for second screen"
onPress={() => navigation.navigate('Second')}
/>
);
}

function Second({
navigation,
}: {
navigation: NativeStackNavigationProp<ParamListBase>;
}) {
return (
<Button
title="Tap me for second screen"
onPress={() => navigation.navigate('First')}
/>
);
}
6 changes: 6 additions & 0 deletions guides/GUIDE_FOR_LIBRARY_AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ Defaults to `auto`.

Sets the translucency of the status bar (similar to the `StatusBar` component). Defaults to `false`.

### `transitionDuration` (iOS only)

Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. The duration of `default` and `flip` transitions isn't customizable.

Defaults to `350`.

### `useTransitionProgress`

Hook providing context value of transition progress of the current screen to be used with `react-native` `Animated`. It consists of 2 values:
Expand Down
1 change: 1 addition & 0 deletions ios/RNSScreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ typedef NS_ENUM(NSInteger, RNSWindowTrait) {
@property (nonatomic) BOOL hasHomeIndicatorHiddenSet;
@property (nonatomic) BOOL customAnimationOnSwipe;
@property (nonatomic) BOOL fullScreenSwipeEnabled;
@property (nonatomic, retain) NSNumber *transitionDuration;

#if !TARGET_OS_TV
@property (nonatomic) RNSStatusBarStyle statusBarStyle;
Expand Down
1 change: 1 addition & 0 deletions ios/RNSScreen.m
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,7 @@ @implementation RNSScreenManager
RCT_EXPORT_VIEW_PROPERTY(replaceAnimation, RNSScreenReplaceAnimation)
RCT_EXPORT_VIEW_PROPERTY(stackPresentation, RNSScreenStackPresentation)
RCT_EXPORT_VIEW_PROPERTY(stackAnimation, RNSScreenStackAnimation)
RCT_EXPORT_VIEW_PROPERTY(transitionDuration, NSNumber)

RCT_EXPORT_VIEW_PROPERTY(onAppear, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onDisappear, RCTDirectEventBlock);
Expand Down
44 changes: 38 additions & 6 deletions ios/RNSScreenStackAnimator.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ - (instancetype)initWithOperation:(UINavigationControllerOperation)operation
{
if (self = [super init]) {
_operation = operation;
_transitionDuration = 0.35; // default duration
_transitionDuration = 0.35; // default duration in seconds
}
return self;
}
Expand All @@ -32,6 +32,12 @@ - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)t
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;
}

Expand Down Expand Up @@ -202,14 +208,30 @@ - (void)animateFadeFromBottomWithTransitionContext:(id<UIViewControllerContextTr
CGAffineTransform topBottomTransform =
CGAffineTransformMakeTranslation(0, 0.08 * transitionContext.containerView.bounds.size.height);

// proportions to default transition duration
NSDictionary *proportionToDefaultTransitionDuration = @{
@"slideOpen" : @1, // 0.35 s
@"fadeOpen" : @0.571, // 0.2 s
@"slideClose" : @0.714, // 0.25 s
@"fadeClose" : @0.428, // 0.15 s
@"fadeCloseDelay" : @0.286, // 0.1 s
kacperkapusciak marked this conversation as resolved.
Show resolved Hide resolved
};

if (_operation == UINavigationControllerOperationPush) {
toViewController.view.transform = topBottomTransform;
toViewController.view.alpha = 0.0;
[[transitionContext containerView] addSubview:toViewController.view];

// defaults to 0.35 s
float slideOpenTransitionDuration =
[self transitionDuration:transitionContext] * [proportionToDefaultTransitionDuration[@"slideOpen"] floatValue];
// defaults to 0.2 s
float fadeOpenTransitionDuration =
[self transitionDuration:transitionContext] * [proportionToDefaultTransitionDuration[@"fadeOpen"] floatValue];

// 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:0.35
[UIView animateWithDuration:slideOpenTransitionDuration
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
Expand All @@ -220,7 +242,7 @@ - (void)animateFadeFromBottomWithTransitionContext:(id<UIViewControllerContextTr
fromViewController.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
[UIView animateWithDuration:0.2
[UIView animateWithDuration:fadeOpenTransitionDuration
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
Expand All @@ -232,9 +254,19 @@ - (void)animateFadeFromBottomWithTransitionContext:(id<UIViewControllerContextTr
toViewController.view.transform = CGAffineTransformIdentity;
[[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];

// defaults to 0.25 s
float slideCloseTransitionDuration =
[self transitionDuration:transitionContext] * [proportionToDefaultTransitionDuration[@"slideClose"] floatValue];
// defaults to 0.15 s
float fadeCloseTransitionDuration =
[self transitionDuration:transitionContext] * [proportionToDefaultTransitionDuration[@"fadeClose"] floatValue];
// defaults to 0.1 s
float fadeCloseDelayDuration = [self transitionDuration:transitionContext] *
[proportionToDefaultTransitionDuration[@"fadeCloseDelay"] floatValue];

// 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:0.25
[UIView animateWithDuration:slideCloseTransitionDuration
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
Expand All @@ -244,8 +276,8 @@ - (void)animateFadeFromBottomWithTransitionContext:(id<UIViewControllerContextTr
completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
[UIView animateWithDuration:0.15
delay:0.1
[UIView animateWithDuration:fadeCloseTransitionDuration
delay:fadeCloseDelayDuration
options:UIViewAnimationOptionCurveLinear
animations:^{
fromViewController.view.alpha = 0.0;
Expand Down
6 changes: 6 additions & 0 deletions native-stack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,12 @@ Using `containedModal` and `containedTransparentModal` with other types of modal

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. The duration of `default` and `flip` transitions isn't customizable.

Defaults to `350`.

#### `useTransitionProgress`

Hook providing context value of transition progress of the current screen to be used with `react-native` `Animated`. It consists of 2 values:
Expand Down
7 changes: 7 additions & 0 deletions src/native-stack/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,13 @@ export type NativeStackNavigationOptions = {
* String that can be displayed in the header as a fallback for `headerTitle`.
*/
title?: string;
/**
* Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS.
* The duration of `default` and `flip` transitions isn't customizable. Defaults to `350`.
*
* @platform ios
*/
transitionDuration?: number;
};

export type NativeStackNavigatorProps = DefaultNavigatorOptions<
Expand Down
2 changes: 2 additions & 0 deletions src/native-stack/views/NativeStackView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ const RouteView = ({
statusBarHidden,
statusBarStyle,
statusBarTranslucent,
transitionDuration,
} = options;

let { stackPresentation = 'push' } = options;
Expand Down Expand Up @@ -211,6 +212,7 @@ const RouteView = ({
statusBarHidden={statusBarHidden}
statusBarStyle={statusBarStyle}
statusBarTranslucent={statusBarTranslucent}
transitionDuration={transitionDuration}
onHeaderBackButtonClicked={() => {
navigation.dispatch({
...StackActions.pop(),
Expand Down
9 changes: 8 additions & 1 deletion src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,13 @@ export interface ScreenProps extends ViewProps {
* @platform android
*/
statusBarTranslucent?: boolean;
/**
* Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS.
* The duration of `default` and `flip` transitions isn't customizable. Defaults to `350`.
*
* @platform ios
*/
transitionDuration?: number;
}

export interface ScreenContainerProps extends ViewProps {
Expand Down Expand Up @@ -460,7 +467,7 @@ export interface SearchBarProps {

/**
* A callback that gets called when search bar is closed
*
*
* @platform android
*/
onClose?: () => void;
Expand Down