[iOS] Unexpected Navigation Behavior When Swiping Back Quickly
Description
When using iOS devices with the following dependencies, swiping back quickly during stack navigation leads to unexpected behavior:
Expected behavior: stack a -> stack b -> stack c -> stack b -> stack a Actual behavior: stack a -> stack b -> stack c -> stack b -> stack a -> stack b -> stack a The issue occurs only when swiping back quickly. When swiping back slowly, the navigation works as expected.
Expected Behavior
https://github.com/user-attachments/assets/ed515fe7-b876-4b45-b1c0-e036575bb62a
Actual Behavior
https://github.com/user-attachments/assets/017bf04d-14dd-49dd-8910-c1f79327c54d
Steps to reproduce
- Enable fullScreenGestureEnabled in the navigation options.
- Set up stack navigation with three screens: stack a, stack b, and stack c.
- Navigate through the stack in order: stack a -> stack b -> stack c.
- Swipe back quickly from stack c to stack b, and then quickly swipe back again from stack b to stack a.
- Observe the unexpected behavior.
Snack or a link to a repository
https://github.com/Unuuuuu/react-native-screens-repro
Screens version
4.3.0
React Native version
0.76.3
Platforms
iOS
JavaScript runtime
None
Workflow
Expo managed workflow
Architecture
Fabric (New Architecture)
Build type
Debug mode
Device
Real device
Device model
iphone 15 pro
Acknowledgements
Yes
Hey! Thanks for reporting. Would it be the same issue as: https://github.com/software-mansion/react-native-screens/issues/2454? It seems similar
Hey! Thanks for reporting. Would it be the same issue as: #2454? It seems similar
Thank you for checking.
I don't think it's the same issue. The issue you mentioned is that when you make a quick gesture, it gets stuck in a bad state, and what I reported is that when you make a quick gesture, it briefly goes to an unintended screen and then returns to the intended screen.
Makes sense, we'll look into it
I have the exact same issue here. Don't think it's similar to #2454 too.
Is there any update on this? It's a pretty annoying bug 😕
@kkafar after multiple tracking native code, the problem is updateContainer may push screen which is disappered, maybe these problem is also related to react-navigation.
For example, If we have screen A,B,C,D : swipe back twice, the right case should be:
viewDidDisappearcalls withscreen DupdateContainercalls , screens only remainingA,B,CviewDidDisappearcalls withscreen CupdateContainercalls, screens only remainingA,B
If swiping quickly, There are two abnormal situations:
-
screen C'sviewDidDisappearcall beforescreen D'sviewDidDisappear -
screen D'sviewDidDisappearcalled, thenscreen C'sviewDidDisappearcalled, thenupdateContainercalled twice, both remainingA,B,C, thescreen Cshould not be there, then the controller try to call appearscreen Cand disapperscreen C, then thescreen Bshows.
I see the comment in RNSScreen.mm method viewWillAppear
// TODO: Find out why this is executed when screen is going out
maybe this because the wrong screen C has been removed but still there
Has there been any development on this? It's still an issue
Is there any update here? I'd really like to switch to the new architecture - and now that it's the default with expo 53, all new apps will be experiencing this issue
Yeah same here, it's a really annoying UI bug. Any updates?
@JustJoostNL @stephentuso @berg-dee
I'm using a shell script to fixed this issue, If you don't use react-navigation's preventRemove, you can run this script after npm install.
I'm using "react-native-screens": "~4.10.0"
#!/bin/bash
#
# Temporary solution to fix swipe issue.
#
# https://github.com/software-mansion/react-native-screens/issues/2559
#
check='disappeared'
screen_header='./node_modules/react-native-screens/ios/RNSScreen.h'
screen_mm='./node_modules/react-native-screens/ios/RNSScreen.mm'
screen_stack_mm='./node_modules/react-native-screens/ios/RNSScreenStack.mm'
if ! grep -qw $check $screen_header; then
sed -i '' 's/(RNSScreenView \*)screenView;/(RNSScreenView *)screenView;\n@property (nonatomic, readonly) BOOL disappeared;/g' $screen_header
echo 'RNSScreen.h hooked'
else
echo 'RNSScreen.h already hooked, skip...'
fi
if ! grep -qw _$check $screen_mm; then
sed -i '' 's/\[super viewWillAppear:animated\];/[super viewWillAppear:animated];\n _disappeared = NO;/g' $screen_mm
sed -i '' 's/\[super viewDidAppear:animated\];/[super viewDidAppear:animated];\n _disappeared = NO;/g' $screen_mm
sed -i '' 's/\[super viewWillDisappear:animated\];/[super viewWillDisappear:animated];\n _disappeared = NO;/g' $screen_mm
sed -i '' 's/\[super viewDidDisappear:animated\];/[super viewDidDisappear:animated];\n _disappeared = YES;/g' $screen_mm
sed -i '' 's/_fakeView = \[UIView new\];/_fakeView = [UIView new];\n _disappeared = NO;/g' $screen_mm
echo 'RNSScreen.mm hooked'
else
echo 'RNSScreen.mm already hooked, skip...'
fi
if ! grep -qw $check $screen_stack_mm; then
sed -i '' 's/\[_controller pushViewController:top animated:YES\];/if(!((RNSScreen *)top).disappeared) {\n [_controller pushViewController:top animated:YES];\n }/g' $screen_stack_mm
echo 'RNSScreenStack.mm hooked'
else
echo 'RNSScreenStack.mm already hooked, skip...'
fi
echo 'hook finish'
Thanks @wvq, I put it in a patch for patch-package or pnpm: https://gist.github.com/stephentuso/af91a8f54ff248125aacc60588ac974c
I don't use preventRemove right now, but what issue does this cause with that?
Same here, this is causing challenges. @wvq would you be able to open a PR if you confirm that your workaround fixes the issue?
Same here
@stephentuso @nzapponi
I opened a PR two months ago, but close it when I found it conflit with preventRemove.
This problem is caused by asynchronously waiting for the sliding animation to end.
If screen stack is A/B/C:
when swipe back twice, it should be:
- Swipe back C
- C animation end callback calls, set screen stack
A/B - Swipe back B
- B animation end callback calls, set screen stack
A
But the callback is async, when swipe quickly, step 2 may run after step 3.
you are already at screen A, but set stack A/B and set stack A both calls,
the screen B will add again then remove, as the video shows.
My PR solution is add a property to mark a screen outdated after it dismissed, when set screen stack, ignore it.
But preventRemove is add a dismissed screen back, which conflit my solution, so I close the PR.
+1 App crashed when scrolling fasterly and go back immediately
@c-info if it's crashing that sounds like a different issue, this is just an animation playing twice
Is there any news on this? For our users this is a really weird UI bug.
thanks a lot!! you saved my life.
react-navigation's
preventRemove
Is this gonna be solved? I think this is an important UX bug affecting too many expo users on iOS
@kkafar Please fix this issue.
Thanks @wvq and @stephentuso for the script and patch. I noticed the patch doesn't work on 4.16.0 for me so I made a new patch here (using bun): https://gist.github.com/austin43/ed1647330d50517de54960eafcb0cc48.
I’m experiencing the same issue on version 4.11.1. It looks like your solution works for my case as well. I really appreciate your support, @wvq . Would it be possible to turn this into a pull request for the next release?
@JustJoostNL @stephentuso @berg-dee
I'm using a shell script to fixed this issue, If you don't use react-navigation's
preventRemove, you can run this script after npm install.I'm using "react-native-screens": "~4.10.0"
#!/bin/bash
Temporary solution to fix swipe issue.
https://github.com/software-mansion/react-native-screens/issues/2559
check='disappeared'
screen_header='./node_modules/react-native-screens/ios/RNSScreen.h' screen_mm='./node_modules/react-native-screens/ios/RNSScreen.mm' screen_stack_mm='./node_modules/react-native-screens/ios/RNSScreenStack.mm'
if ! grep -qw $check $screen_header; then sed -i '' 's/(RNSScreenView *)screenView;/(RNSScreenView *)screenView;\n@property (nonatomic, readonly) BOOL disappeared;/g' $screen_header echo 'RNSScreen.h hooked' else echo 'RNSScreen.h already hooked, skip...' fi
if ! grep -qw _$check $screen_mm; then sed -i '' 's/[super viewWillAppear:animated];/[super viewWillAppear:animated];\n _disappeared = NO;/g' $screen_mm sed -i '' 's/[super viewDidAppear:animated];/[super viewDidAppear:animated];\n _disappeared = NO;/g' $screen_mm sed -i '' 's/[super viewWillDisappear:animated];/[super viewWillDisappear:animated];\n _disappeared = NO;/g' $screen_mm sed -i '' 's/[super viewDidDisappear:animated];/[super viewDidDisappear:animated];\n _disappeared = YES;/g' $screen_mm
sed -i '' 's/_fakeView = [UIView new];/_fakeView = [UIView new];\n _disappeared = NO;/g' $screen_mm
echo 'RNSScreen.mm hooked' else echo 'RNSScreen.mm already hooked, skip...' fi
if ! grep -qw $check $screen_stack_mm; then sed -i '' 's/[_controller pushViewController:top animated:YES];/if(!((RNSScreen *)top).disappeared) {\n [_controller pushViewController:top animated:YES];\n }/g' $screen_stack_mm echo 'RNSScreenStack.mm hooked' else echo 'RNSScreenStack.mm already hooked, skip...' fi
echo 'hook finish'
Same problem here, I will try these implementation
This issue is still present as of November 2025 on "react-native-screens": "~4.16.0" Any updates on this?