Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into fix-random-freezes-on…
Browse files Browse the repository at this point in the history
…-ios-when-using-modal
  • Loading branch information
alduzy committed Nov 8, 2024
2 parents d0f7761 + b38c590 commit 51df9e7
Show file tree
Hide file tree
Showing 25 changed files with 686 additions and 239 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,13 @@ Screens are already integrated with the React Native's most popular navigation l

## Supported react-native version

### Support for Paper

Paper is the default rendering system for React Native versions prior to 0.76.

| library version | react-native version |
| --------------- | -------------------- |
| 3.33.0+ | 0.72.0+
| 3.33.0+ | 0.72.0+ |
| 3.32.0+ | 0.71.0+ |
| 3.30.0+ | 0.68.0+ |
| 3.14.0+ | 0.64.0+ |
Expand All @@ -123,6 +127,7 @@ Here's a table with summary of supported `react-native` versions when Fabric is

| library version | react-native version |
| --------------- | -------------------- |
| 3.35.0+ | 0.76.0+ |
| 3.33.0+ | 0.75.0+ |
| 3.32.0+ | 0.74.0+ |
| 3.28.0+ | 0.73.0+ |
Expand All @@ -134,7 +139,7 @@ Here's a table with summary of supported `react-native` versions when Fabric is
## Usage with [react-navigation](https://github.com/react-navigation/react-navigation)

> [!CAUTION]
> NativeStack has been moved from react-native-screens/native-stack to @react-navigation/native since version v6. With react-native-screens v4 native stack v5 (react-native-screens/native-stack) is deprecated and marked for removal in the upcoming minor release, react-native-screens v4 will support only @react-navigation/native-stack v7.
> JS API of the native stack has been moved from `react-native-screens/native-stack` to `@react-navigation/native-stack` since version v6. Currently, native stack v5 (imported from `react-native-screens/native-stack`) is deprecated and will be removed in the upcoming **minor** release. `react-native-screens` v4 will support only `@react-navigation/native-stack` v7.
Screens support is built into [react-navigation](https://github.com/react-navigation/react-navigation) starting from version [2.14.0](https://github.com/react-navigation/react-navigation/releases/tag/2.14.0) for all the different navigator types (stack, tab, drawer, etc).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void setProperty(T view, String propName, @Nullable Object value) {
mViewManager.setFullScreenSwipeEnabled(view, value == null ? false : (boolean) value);
break;
case "fullScreenSwipeShadowEnabled":
mViewManager.setFullScreenSwipeShadowEnabled(view, value == null ? false : (boolean) value);
mViewManager.setFullScreenSwipeShadowEnabled(view, value == null ? true : (boolean) value);
break;
case "homeIndicatorHidden":
mViewManager.setHomeIndicatorHidden(view, value == null ? false : (boolean) value);
Expand Down 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
87 changes: 87 additions & 0 deletions apps/src/tests/TestScreenAnimationV5.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { View, StyleSheet, Button } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import {
NativeStackNavigationProp,
createNativeStackNavigator,
} from 'react-native-screens/native-stack'
import { GestureDetectorProvider } from 'react-native-screens/gesture-handler';

type StackParamList = {
ScreenA: undefined;
ScreenB: undefined;
ScreenC: undefined;
};

interface MainScreenProps {
navigation: NativeStackNavigationProp<StackParamList, 'ScreenA'>;
}

const MainScreen = ({ navigation }: MainScreenProps): JSX.Element => (
<View style={{ ...styles.container, backgroundColor: 'moccasin' }}>
<Button
title="Go ScreenB"
onPress={() => {
navigation.navigate('ScreenB');
}}
/>
<Button onPress={() => navigation.pop()} title="🔙 Back to Examples" />
</View>
);

interface ScreenBProps {
navigation: NativeStackNavigationProp<StackParamList, 'ScreenB'>;
}

const ScreenB = ({ navigation }: ScreenBProps): JSX.Element => (
<View style={{ ...styles.container, backgroundColor: 'thistle' }}>
<Button title="Go ScreenC" onPress={() => navigation.navigate('ScreenC')} />
<Button title="Go back" onPress={() => navigation.goBack()} />
</View>
);

interface ScreenCProps {
navigation: NativeStackNavigationProp<StackParamList, 'ScreenC'>;
}

const ScreenC = ({ navigation }: ScreenCProps): JSX.Element => (
<View style={{ ...styles.container, backgroundColor: 'blue' }}>
<Button title="Go back" onPress={() => navigation.goBack()} />
</View>
);

const Stack = createNativeStackNavigator<StackParamList>();

const App = (): JSX.Element => (
<GestureHandlerRootView style={{ flex: 1 }}>
<NavigationContainer>
<GestureDetectorProvider>
<Stack.Navigator
screenOptions={{
headerBackVisible: false,
animation: 'none',
}}>
<Stack.Screen name="ScreenA" component={MainScreen} />
<Stack.Screen
name="ScreenB"
component={ScreenB}
options={{
goBackGesture: 'twoDimensionalSwipe',
}}
/>
<Stack.Screen name="ScreenC" component={ScreenC} />
</Stack.Navigator>
</GestureDetectorProvider>
</NavigationContainer>
</GestureHandlerRootView>
);

const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 10,
},
});

export default App;
1 change: 1 addition & 0 deletions apps/src/tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export { default as Test2332 } from './Test2332';
export { default as Test2379 } from './Test2379';
export { default as Test2395 } from './Test2395';
export { default as TestScreenAnimation } from './TestScreenAnimation';
export { default as TestScreenAnimationV5 } from './TestScreenAnimationV5';
export { default as TestHeader } from './TestHeader';
export { default as TestPreload } from './TestPreload';
export { default as TestActivityStateProgression } from './TestActivityStateProgression';
Expand Down
6 changes: 3 additions & 3 deletions guides/GUIDE_FOR_LIBRARY_AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Boolean indicating whether the swipe gesture should work on whole screen. Swipin

Boolean indicating whether the full screen dismiss gesture has shadow under view during transition. The gesture uses custom transition and thus
doesn't have a shadow by default. When enabled, a custom shadow view is added during the transition which tries to mimic the
default iOS shadow. Defaults to `false`.
default iOS shadow. Defaults to `true`.

### `gestureEnabled` (iOS only)

Expand Down Expand Up @@ -223,7 +223,7 @@ Allows for the customization of how the given screen should appear/disappear whe
- `"fade"` – fades screen in or out
- `fade_from_bottom` – performs a fade from bottom animation
- `"flip"` – flips the screen, requires `stackPresentation: "modal"` (iOS only)
- `"simple_push"` – performs a default animation, but without shadow and native header transition (iOS only)
- `"simple_push"` – performs a default animation, but without native header transition (iOS only)
- `"slide_from_bottom"` - slide in the new screen from bottom to top
- `"slide_from_right"` - slide in the new screen from right to left (Android only, resolves to default transition on iOS)
- `"slide_from_left"` - slide in the new screen from left to right
Expand Down 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 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.

- (void)updateInteractiveTransition:(CGFloat)percentComplete
{
if (_animationController != nil) {
[_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 = _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
1 change: 1 addition & 0 deletions ios/RNSScreen.mm
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ - (void)initCommonProps
_hasOrientationSet = NO;
_hasHomeIndicatorHiddenSet = NO;
_activityState = RNSActivityStateUndefined;
_fullScreenSwipeShadowEnabled = YES;
#if !TARGET_OS_TV
_sheetExpandsWhenScrolledToEdge = YES;
#endif // !TARGET_OS_TV
Expand Down
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) {
[_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
14 changes: 13 additions & 1 deletion ios/RNSScreenStackAnimator.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@

@interface RNSScreenStackAnimator : NSObject <UIViewControllerAnimatedTransitioning>

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

/// 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

0 comments on commit 51df9e7

Please sign in to comment.