Skip to content

Commit

Permalink
feat: add transition duration on iOS (#1259)
Browse files Browse the repository at this point in the history
## Description

Added transition duration on iOS requested in #1171.

The prop changes the duration of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. The duration of `default` and `flip` transitions isn't customizable.

Such prop could be also added on `Android`, but each of fragments have its own transition duration, so the way it should be applied would need to be thought over. One of options is to set the duration of currently added or currently removed fragment on both of them.
  • Loading branch information
WoLewicki authored Feb 7, 2022
1 parent 69de602 commit e27baec
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 7 deletions.
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. Defaults to `350`.

The duration of `default` and `flip` transitions isn't customizable.

### `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,
@"fadeOpen" : @(0.2 / 0.35),
@"slideClose" : @(0.25 / 0.35),
@"fadeClose" : @(0.15 / 0.35),
@"fadeCloseDelay" : @(0.1 / 0.35),
};

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. Defaults to `350`.

The duration of `default` and `flip` transitions isn't customizable.

#### `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. Defaults to `350`.
* The duration of `default` and `flip` transitions isn't customizable.
*
* @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. Defaults to `350`.
* The duration of `default` and `flip` transitions isn't customizable.
*
* @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

0 comments on commit e27baec

Please sign in to comment.