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: expose new container for 2-state navigators #1029

Merged
merged 12 commits into from
Sep 6, 2021
4 changes: 2 additions & 2 deletions TestsExample/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ PODS:
- React-RCTVibration
- ReactCommon/turbomodule/core
- Yoga
- RNScreens (3.5.0):
- RNScreens (3.6.0):
- React-Core
- React-RCTImage
- RNVectorIcons (7.1.0):
Expand Down Expand Up @@ -582,7 +582,7 @@ SPEC CHECKSUMS:
RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
RNReanimated: f89561404f0efaa5352b627dc35e5a97a845a2cd
RNScreens: 01ab149b5dd5c27f5ff26741b1d2bdf2cee1af35
RNScreens: eb0dfb2d6b21d2d7f980ad46b14eb306d2f1062e
RNVectorIcons: bc69e6a278b14842063605de32bec61f0b251a59
Yoga: aa0cb45287ebe1004c02a13f279c55a95f1572f4
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
Expand Down
172 changes: 62 additions & 110 deletions TestsExample/src/Test619.js
Original file line number Diff line number Diff line change
@@ -1,110 +1,62 @@
import React, {useEffect, useState} from 'react';
import {View, Text, FlatList, ActivityIndicator} from 'react-native';
import {createStackNavigator} from '@react-navigation/stack';
import {createNativeStackNavigator} from 'react-native-screens/native-stack';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {NavigationContainer} from '@react-navigation/native';

const Stack1 = createNativeStackNavigator();

const Tabs = createBottomTabNavigator();

const Stack2 = createNativeStackNavigator();

const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];

const Screen1 = () => {
return (
<FlatList
contentInsetAdjustmentBehavior={'always'}
data={data}
onRefresh={() => {}}
refreshing={false}
keyExtractor={(item) => `${item}`}
renderItem={({item}) => {
return (
<View
style={{
backgroundColor: item % 2 === 0 ? 'red' : 'green',
height: 120,
}}
/>
);
}}
style={{flex: 1}}
/>
);
};

const Tab1 = () => {
return (
<Stack2.Navigator>
<Stack2.Screen
name={'Screen 1'}
component={Screen1}
options={{
headerLeft: () => <Text>Left</Text>,
headerRight: () => <Text>Right</Text>,
headerLargeTitle: true,
headerLargeTitleHideShadow: true,
}}
/>
</Stack2.Navigator>
);
};

const Tab2 = () => {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text>Tab 2</Text>
</View>
);
};

const TabsScreen = () => {
return (
<Tabs.Navigator>
<Tabs.Screen name={'Tab 1'} component={Tab1} />
<Tabs.Screen name={'Tab 2'} component={Tab2} />
</Tabs.Navigator>
);
};

const Init = () => {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<ActivityIndicator size={'large'} />
</View>
);
};

const App = () => {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
setTimeout(() => {
setLoaded(true);
}, 500);
}, []);
return (
<NavigationContainer>
<Stack1.Navigator>
{loaded && (
<Stack1.Screen
name={'Tabs'}
component={TabsScreen}
options={{headerShown: false}}
/>
)}
{!loaded && (
<Stack1.Screen
name={'Loading'}
component={Init}
options={{headerShown: false}}
/>
)}
</Stack1.Navigator>
</NavigationContainer>
);
};

export default App;
import React from 'react';
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from "react-native-screens/native-stack"
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import { createStackNavigator } from '@react-navigation/stack'
import { View, Text, Button } from 'react-native';
import { useNavigation } from "@react-navigation/native";

const ParentStack = createNativeStackNavigator()
const BottomTab = createBottomTabNavigator()
const ChildStack = createNativeStackNavigator()
const Tab1Stack = createNativeStackNavigator()

const DummyContent = () => {
const navigation = useNavigation();

return <View style={{flex:1,justifyContent:"center",alignItems:"center", backgroundColor: 'red'}}>
<Text style={{marginBottom:20, textAlign:"center"}}>Child Stack</Text>
<Button title="Go Back" onPress={() => navigation.goBack()}/>
</View>
}

const ChildStackScreen = () => (
<ChildStack.Navigator screenOptions={{headerShown: false, headerLargeTitle: false}}>
<Tab1Stack.Screen name="ChildStack" component={BottomStackScreen} />
</ChildStack.Navigator>
)

const Another = () => (
<ChildStack.Navigator screenOptions={{headerShown: true, headerLargeTitle: false}}>
<Tab1Stack.Screen name="ChildStack1" component={InitialScreen} />
</ChildStack.Navigator>
)

const AnotherBottomTabs = () => (
<BottomTab.Navigator screenOptions={{headerShown: false}} detachInactiveScreens={true}>
<BottomTab.Screen name="Tab2" component={ChildStackScreen} />
</BottomTab.Navigator>
)

const BottomStackScreen = () => (
<BottomTab.Navigator screenOptions={{headerShown: false}} detachInactiveScreens={true}>
<BottomTab.Screen name="Tab1" component={Another} />
</BottomTab.Navigator>
)

const InitialScreen = () => {
const navigation = useNavigation();

return <View style={{flex:1,justifyContent:"center",alignItems:"center", backgroundColor: 'red'}}><Button title="Click" onPress={() => navigation.navigate('Bottom')}/></View>
}

const App = () => (
<NavigationContainer>
<ParentStack.Navigator>
<ParentStack.Screen name="Initial" component={BottomStackScreen} options={{ headerShown: false, stackAnimation: 'default' /** set none to fix */ }} />
<ParentStack.Screen name="Bottom" component={ChildStackScreen} options={{ headerShown: false }}></ParentStack.Screen>
</ParentStack.Navigator>
</NavigationContainer>
);

export default App;
2 changes: 0 additions & 2 deletions ios/RNSScreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

#import "RNSScreenContainer.h"

@class RNSScreenContainerView;

typedef NS_ENUM(NSInteger, RNSScreenStackPresentation) {
RNSScreenStackPresentationPush,
RNSScreenStackPresentationModal,
Expand Down
11 changes: 5 additions & 6 deletions ios/RNSScreen.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ @implementation RNSScreenView {
CGRect _reactFrame;
}

@synthesize controller = _controller;

- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
Expand All @@ -45,10 +43,10 @@ - (void)reactSetFrame:(CGRect)frame
{
_reactFrame = frame;
UIViewController *parentVC = self.reactViewController.parentViewController;
if (parentVC != nil && ![parentVC isKindOfClass:[UINavigationController class]]) {
if (parentVC != nil && ![parentVC isKindOfClass:[RNScreensNavigationController class]]) {
[super reactSetFrame:frame];
}
// when screen is mounted under UINavigationController it's size is controller
// when screen is mounted under RNScreensNavigationController it's size is controller
// by the navigation controller itself. That is, it is set to fill space of
// the controller. In that case we ignore react layout system from managing
// the screen dimensions and we wait for the screen VC to update and then we
Expand Down Expand Up @@ -500,11 +498,12 @@ - (void)viewDidLayoutSubviews
[super viewDidLayoutSubviews];

// The below code makes the screen view adapt dimensions provided by the system. We take these
// into account only when the view is mounted under UINavigationController in which case system
// into account only when the view is mounted under RNScreensNavigationController in which case system
// provides additional padding to account for possible header, and in the case when screen is
// shown as a native modal, as the final dimensions of the modal on iOS 12+ are shorter than the
// screen size
BOOL isDisplayedWithinUINavController = [self.parentViewController isKindOfClass:[UINavigationController class]];
BOOL isDisplayedWithinUINavController =
[self.parentViewController isKindOfClass:[RNScreensNavigationController class]];
BOOL isPresentedAsNativeModal = self.parentViewController == nil && self.presentingViewController != nil;
if ((isDisplayedWithinUINavController || isPresentedAsNativeModal) &&
!CGRectEqualToRect(_lastViewFrame, self.view.frame)) {
Expand Down
11 changes: 10 additions & 1 deletion ios/RNSScreenContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@

@end

@interface RNSScreenContainerView : UIView <RNSScreenContainerDelegate>
@interface RNSScreenContainerManager : RCTViewManager

@end

@interface RNSScreenContainerView : UIView <RNSScreenContainerDelegate, RCTInvalidating>

@property (nonatomic, retain) UIViewController *controller;
@property (nonatomic, retain) NSMutableArray *reactSubviews;

- (void)maybeDismissVC;

@end
73 changes: 25 additions & 48 deletions ios/RNSScreenContainer.m
Original file line number Diff line number Diff line change
@@ -1,26 +1,6 @@
#import "RNSScreenContainer.h"
#import "RNSScreen.h"

#import <React/RCTUIManager.h>
#import <React/RCTUIManagerObserverCoordinator.h>
#import <React/RCTUIManagerUtils.h>

@interface RNSScreenContainerManager : RCTViewManager

- (void)markUpdated:(RNSScreenContainerView *)screen;

@end

@interface RNSScreenContainerView () <RCTInvalidating>

@property (nonatomic, retain) UIViewController *controller;
@property (nonatomic, retain) NSMutableSet<RNSScreenView *> *activeScreens;
@property (nonatomic, retain) NSMutableArray<RNSScreenView *> *reactSubviews;

- (void)updateContainer;

@end

@implementation RNScreensViewController

#if !TARGET_OS_TV
Expand All @@ -43,6 +23,7 @@ - (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return [self findActiveChildVC].supportedInterfaceOrientations;
}
#endif

- (UIViewController *)findActiveChildVC
{
Expand All @@ -54,39 +35,39 @@ - (UIViewController *)findActiveChildVC
}
return [[self childViewControllers] lastObject];
}
#endif

@end

@implementation RNSScreenContainerView {
BOOL _needUpdate;
BOOL _invalidated;
__weak RNSScreenContainerManager *_manager;
NSMutableSet *_activeScreens;
}

- (instancetype)initWithManager:(RNSScreenContainerManager *)manager
- (instancetype)init
{
if (self = [super init]) {
_activeScreens = [NSMutableSet new];
_reactSubviews = [NSMutableArray new];
_controller = [[RNScreensViewController alloc] init];
_needUpdate = NO;
[self setupController];
_invalidated = NO;
_manager = manager;
[self addSubview:_controller.view];
}
return self;
}

- (void)setupController
{
_controller = [[RNScreensViewController alloc] init];
[self addSubview:_controller.view];
}

- (void)markChildUpdated
{
// We want 'updateContainer' to be executed on main thread after all enqueued operations in
// uimanager are complete. For that we collect all marked containers in manager class and enqueue
// operation on ui thread that should run once all the updates are completed.
if (!_needUpdate) {
_needUpdate = YES;
[_manager markUpdated:self];
}
// We want the attaching/detaching of children to be always made on main queue, which
// is currently true for `react-navigation` since this method is triggered
// by the changes of `Animated` value in stack's transition or adding/removing screens
// in all navigators
RCTAssertMainQueue();
[self updateContainer];
}

- (void)insertReactSubview:(RNSScreenView *)subview atIndex:(NSInteger)atIndex
Expand Down Expand Up @@ -153,7 +134,6 @@ - (void)attachScreen:(RNSScreenView *)screen atIndex:(NSInteger)index

- (void)updateContainer
{
_needUpdate = NO;
BOOL screenRemoved = NO;
// remove screens that are no longer active
NSMutableSet *orphaned = [NSMutableSet setWithSet:_activeScreens];
Expand Down Expand Up @@ -200,8 +180,14 @@ - (void)updateContainer
}
}

if ((screenRemoved || screenAdded) && _controller.presentedViewController == nil &&
_controller.presentingViewController == nil) {
if (screenRemoved || screenAdded) {
[self maybeDismissVC];
}
}

- (void)maybeDismissVC
{
if (_controller.presentedViewController == nil && _controller.presentingViewController == nil) {
// if user has reachability enabled (one hand use) and the window is slided down the below
// method will force it to slide back up as it is expected to happen with UINavController when
// we push or pop views.
Expand Down Expand Up @@ -252,16 +238,7 @@ @implementation RNSScreenContainerManager

- (UIView *)view
{
return [[RNSScreenContainerView alloc] initWithManager:self];
}

- (void)markUpdated:(RNSScreenContainerView *)container
{
// we want the attaching/detaching of children to be always made on main queue, which
// is currently true for `react-navigation` since the changes of `Animated` value in stack's
// transition and mounting/unmounting views in tabs and drawer are dispatched on main queue.
RCTAssertMainQueue();
[container updateContainer];
return [[RNSScreenContainerView alloc] init];
}

@end
Loading