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

fix(iOS): Crash while pushing n different screens at the same time #2249

Merged
merged 10 commits into from
Jul 26, 2024
107 changes: 107 additions & 0 deletions apps/src/tests/Test2235.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import {
createNativeStackNavigator,
NativeStackNavigationProp,
} from '@react-navigation/native-stack';
import { Button } from '../shared';
import { NavigationContainer, ParamListBase } from '@react-navigation/native';

interface RouteProps {
navigation: NativeStackNavigationProp<ParamListBase>;
}

const MainScreen = ({ navigation }: RouteProps): React.JSX.Element => (
<View style={{ ...styles.container, backgroundColor: 'moccasin' }}>
<Button
title="Push 2 screens at once"
onPress={() => {
navigation.navigate('Detail');
navigation.navigate('Detail2');
}}
/>
<Button
title="Open nested stack"
onPress={() => {
navigation.navigate('NestedStack');
}}
/>
</View>
);

const NestedMainScreen = ({ navigation }: RouteProps): React.JSX.Element => (
<View style={{ ...styles.container, backgroundColor: 'moccasin' }}>
<Button
title="Go to detail"
onPress={() => {
navigation.navigate('Detail');
navigation.navigate('Detail2');
}}
/>
<Button
title="Pop stack"
onPress={() => {
navigation.goBack();
}}
/>
</View>
);


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

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

const NestedStackScreen = (): React.JSX.Element => (
<NestedStack.Navigator screenOptions={{ headerBackVisible: false }}>
<NestedStack.Screen name="NestedDetail" component={NestedMainScreen} />
<Stack.Screen name="Detail" component={DetailScreen} />
<Stack.Screen name="Detail2" component={DetailScreen2} />
</NestedStack.Navigator>
)

const Stack = createNativeStackNavigator<ParamListBase>();
const NestedStack = createNativeStackNavigator<ParamListBase>();

const App = (): React.JSX.Element => (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerBackVisible: false,
}}>
<Stack.Screen
name="Main"
component={MainScreen}
options={{ title: 'Simple Native Stack' }}
/>
<Stack.Screen name="Detail" component={DetailScreen} />
<Stack.Screen name="Detail2" component={DetailScreen2} />
<Stack.Screen name="NestedStack" component={NestedStackScreen} />
</Stack.Navigator>
</NavigationContainer>
);

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 @@ -105,5 +105,6 @@ export { default as Test2223 } from './Test2223';
export { default as Test2227 } from './Test2227';
export { default as Test2229 } from './Test2229';
export { default as Test2231 } from './Test2231';
export { default as Test2235 } from './Test2235';
export { default as TestScreenAnimation } from './TestScreenAnimation';
export { default as TestHeader } from './TestHeader';
33 changes: 26 additions & 7 deletions ios/RNSScreenStack.mm
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ @interface RNSScreenStackView () <
UINavigationControllerDelegate,
UIAdaptivePresentationControllerDelegate,
UIGestureRecognizerDelegate,
UIViewControllerTransitioningDelegate>
UIViewControllerTransitioningDelegate
#ifdef RCT_NEW_ARCH_ENABLED
,
RCTMountingTransactionObserving
#endif
>

@property (nonatomic) NSMutableArray<UIViewController *> *presentedModals;
@property (nonatomic) BOOL updatingModals;
Expand Down Expand Up @@ -1112,9 +1117,8 @@ - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childCompone

[_reactSubviews insertObject:(RNSScreenView *)childComponentView atIndex:index];
((RNSScreenView *)childComponentView).reactSuperview = self;
dispatch_async(dispatch_get_main_queue(), ^{
[self maybeAddToParentAndUpdateContainer];
});
// Container update is done after all mount operations are executed in
// `- [RNSScreenStackView mountingTransactionDidMount: withSurfaceTelemetry:]`
}

- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
Expand Down Expand Up @@ -1149,9 +1153,6 @@ - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childCompo
screenChildComponent.reactSuperview = nil;
[_reactSubviews removeObject:screenChildComponent];
[screenChildComponent removeFromSuperview];
dispatch_async(dispatch_get_main_queue(), ^{
[self maybeAddToParentAndUpdateContainer];
});
}

- (void)takeSnapshot
Expand All @@ -1163,6 +1164,24 @@ - (void)takeSnapshot
}
}

- (void)mountingTransactionDidMount:(const facebook::react::MountingTransaction &)transaction
withSurfaceTelemetry:(const facebook::react::SurfaceTelemetry &)surfaceTelemetry
{
for (const auto &mutation : transaction.getMutations()) {
if (mutation.parentShadowView.tag == self.tag &&
(mutation.type == react::ShadowViewMutation::Type::Insert ||
mutation.type == react::ShadowViewMutation::Type::Remove)) {
// we need to wait until children have their layout set. At this point they don't have the layout
// set yet, however the layout call is already enqueued on ui thread. Enqueuing update call on the
// ui queue will guarantee that the update will run after layout.
dispatch_async(dispatch_get_main_queue(), ^{
[self maybeAddToParentAndUpdateContainer];
});
break;
}
}
}

- (void)prepareForRecycle
{
[super prepareForRecycle];
Expand Down
Loading