react-native-screens icon indicating copy to clipboard operation
react-native-screens copied to clipboard

[iOS] Unexpected Navigation Behavior When Swiping Back Quickly

Open Unuuuuu opened this issue 1 year ago • 26 comments

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

  1. Enable fullScreenGestureEnabled in the navigation options.
  2. Set up stack navigation with three screens: stack a, stack b, and stack c.
  3. Navigate through the stack in order: stack a -> stack b -> stack c.
  4. Swipe back quickly from stack c to stack b, and then quickly swipe back again from stack b to stack a.
  5. 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

Unuuuuu avatar Dec 09 '24 05:12 Unuuuuu

Hey! Thanks for reporting. Would it be the same issue as: https://github.com/software-mansion/react-native-screens/issues/2454? It seems similar

kkafar avatar Dec 09 '24 10:12 kkafar

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.

Unuuuuu avatar Dec 10 '24 01:12 Unuuuuu

Makes sense, we'll look into it

kkafar avatar Dec 11 '24 07:12 kkafar

I have the exact same issue here. Don't think it's similar to #2454 too.

JustJoostNL avatar Dec 26 '24 12:12 JustJoostNL

Is there any update on this? It's a pretty annoying bug 😕

JustJoostNL avatar Jan 14 '25 14:01 JustJoostNL

@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:

  1. viewDidDisappear calls with screen D
  2. updateContainer calls , screens only remaining A,B,C
  3. viewDidDisappear calls with screen C
  4. updateContainer calls, screens only remaining A,B

If swiping quickly, There are two abnormal situations:

  1. screen C's viewDidDisappear call before screen D's viewDidDisappear

  2. screen D's viewDidDisappear called, then screen C's viewDidDisappear called, then updateContainer called twice, both remaining A,B,C, the screen C should not be there, then the controller try to call appear screen C and disapper screen C, then the screen B shows.

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

wvq avatar Mar 09 '25 10:03 wvq

Has there been any development on this? It's still an issue

berg-dee avatar Apr 09 '25 07:04 berg-dee

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

stephentuso avatar May 03 '25 02:05 stephentuso

Yeah same here, it's a really annoying UI bug. Any updates?

JustJoostNL avatar May 09 '25 16:05 JustJoostNL

@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'

wvq avatar May 10 '25 13:05 wvq

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?

stephentuso avatar May 14 '25 00:05 stephentuso

Same here, this is causing challenges. @wvq would you be able to open a PR if you confirm that your workaround fixes the issue?

nzapponi avatar May 14 '25 09:05 nzapponi

Same here

DigYang avatar May 20 '25 10:05 DigYang

@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:

  1. Swipe back C
  2. C animation end callback calls, set screen stack A/B
  3. Swipe back B
  4. 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.

wvq avatar May 22 '25 04:05 wvq

+1 App crashed when scrolling fasterly and go back immediately

c-info avatar May 31 '25 11:05 c-info

@c-info if it's crashing that sounds like a different issue, this is just an animation playing twice

stephentuso avatar Jun 02 '25 13:06 stephentuso

Is there any news on this? For our users this is a really weird UI bug.

JustJoostNL avatar Aug 14 '25 10:08 JustJoostNL

thanks a lot!! you saved my life.

react-navigation's preventRemove

GeekJC avatar Sep 04 '25 12:09 GeekJC

Is this gonna be solved? I think this is an important UX bug affecting too many expo users on iOS

mberenguer-adding avatar Sep 11 '25 06:09 mberenguer-adding

@kkafar Please fix this issue.

fe-dudu avatar Sep 20 '25 08:09 fe-dudu

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.

austin43 avatar Oct 13 '25 23:10 austin43

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'

locnp-active avatar Nov 14 '25 06:11 locnp-active

Same problem here, I will try these implementation

devGuerra avatar Nov 18 '25 23:11 devGuerra

This issue is still present as of November 2025 on "react-native-screens": "~4.16.0" Any updates on this?

adnan-smlatic avatar Nov 24 '25 12:11 adnan-smlatic