software-mansion / react-native-screens

Native navigation primitives for your React Native app.
https://docs.swmansion.com/react-native-screens/
MIT License
3.02k stars 513 forks source link

[Ios] app lags/stutters while navigating to a new screen with auto focused text input #1637

Open itsramiel opened 1 year ago

itsramiel commented 1 year ago

Description

When navigating from one screen to another in a stack where the second screen has a text input with autofocus, the transition animation between the screens becomes laggy.

The issue is more obvious on a real device

Real device (Iphone 8 plus):

https://user-images.githubusercontent.com/80689446/201928425-d0af3930-867c-4f2e-87be-92d2d333bc1b.MP4

Simulator

https://user-images.githubusercontent.com/80689446/201928484-e38b3ae0-4b77-48a9-897c-61112d51154d.mp4

Steps to reproduce

  1. create a native stack navigation with two screens
  2. add a text input with autofocus in Screen 2
  3. navigate from Screen 1 to Screen 2

Snack or a link to a repository

https://github.com/itsramiel/bare-autocus

Screens version

3.18.2

React Native version

0.70.5

Platforms

iOS

JavaScript runtime

Hermes

Workflow

React Native (without Expo)

Architecture

Paper (Old Architecture)

Build type

Debug mode

Device

Real device

Device model

Iphone 8 plus( Ios 16.0 )

Acknowledgements

Yes

hirbod commented 1 year ago

I can confirm this. Happening for me as well. I also saw this on other apps (Like Lieferando / Delivery.com), which exposed to me (what I was already guessing) that they use RN 🥸

itsramiel commented 1 year ago

I can confirm this. Happening for me as well. I also saw this on other apps (Like Lieferando / Delivery.com), which exposed to me (what I was already guessing) that they use RN 🥸

Happens on Facebook's messenger chat too

kkafar commented 1 year ago

Hi @itsramiel,

do you have any insights when this started to happen? Is it an issue since 3.18.2? I guess I will test this, but if you happened to have this info it would be great :-)

kkafar commented 1 year ago

First conclusions:

  1. I believe app does not actually lag.
  2. What happens is that focus is applied way too late after the animation starts and it looks like first animation is cancelled and then second one is played with focused text input.

In React Native code focus is applied in didMoveToWindow method. What boggles me is that this code was introduced in 2020 (earlier it was done imperatively from JS), so this bug should not be new (not much changed recently in screens iOS code). I'll be back with more details once I play with RN code.

itsramiel commented 1 year ago

I am actually confused too because I have an app that was on

react-native: 0.69.5
react-natie-screens: 3.15.0

which was working fine with autofocus but when I updated to

react-native: 0.70.5
react-natie-screens: 3.18.2

then it started to happen, but if I create a fresh project with old dependencies I still get the buggy behavior 🤦🏻‍♂️. I have no clue really

hirbod commented 1 year ago

Could this be related to iOS 16?

kkafar commented 1 year ago

So I moved the code I linked earlier from didMoveToWindow to willMoveToWindow: method and the behaviour improved - animation no longer "goes back".

See the recording below. On the left is reproduction. On the right you can see behaviour after changes in RN code.

https://user-images.githubusercontent.com/50801299/202730732-89a646ea-075a-48a1-a469-f06d7143ea5b.mov

TextView still jumps, but I'm not sure I can do much about it, as keyboard's view gets created only after reactFocus method is called, thus it is after the animation start => the transiting view is updated few frames later.

kkafar commented 1 year ago

I can also confirm that the issue does not occur on iOS 15.5 (I'm running on simulator)

Keyboard is perfectly synchronised with keyboard...

https://user-images.githubusercontent.com/50801299/202733397-54199265-46c0-403a-a32e-d18fe709f7a7.mov

It might be hard to fix in on react-native-screens side but will do some more experimenting.

itsramiel commented 1 year ago

Thank you @kkafar for the investigation. If you think this is not a react-native-screens issues please let me know where is the correct place to do that if possible.

kkafar commented 1 year ago

I believe this issue is caused by some internal iOS changes made by Apple, as I've heard that it also concerns fully native applications. It would be best to report this straight to Apple (I haven't checked whether somebody has done it yet) via their developer.apple service.

Also I've been experimenting some more, but I haven't managed to achieve better results than these I mentioned earlier.

michele6000 commented 1 year ago

Someone has find out some fix?

psppro26 commented 1 year ago

same issue ! any updates ?

cliffordrice3 commented 1 year ago

Seeing this same issue. Looks like it was also an issue about 2 years ago?

Does anyone know if there is an open issue w/ react-native or Apple regarding this?

kevinklassen commented 1 year ago

Any updates on this issue? Still getting the stutter when switching to a screen with autoFocus on a TextInput on iOS 16.2. Are there any known workarounds?

kevinklassen commented 1 year ago

A workaround is to delay the autofocus of the TextInput until after the screen is rendered. Please find an example below where it waits 500ms from when the screen is focused to focus on the TextInput. autoFocus on the TextInput itself is set to false.

// References for email input to use for focus
const inputRef = useRef(null);

// State to track whether the email input should be focused
const [shouldFocus, setShouldFocus] = useState(false);

// Wait for 500ms before setting the autoFocus prop to true
useEffect(() => {
  const timeout = setTimeout(() => {
    setShouldFocus(true);
  }, 500);

  return () => clearTimeout(timeout);
}, []);

// Set focus on the input element when shouldFocus is true
useEffect(() => {
  if (shouldFocus) {
    inputRef.current?.focus();
  }
}, [shouldFocus]);
bryanltobing commented 1 year ago

still happening on react native v0.72.3. is there a better workaround instead of delaying the focus?

kostas64 commented 4 months ago

Hi guys, is there any news about this issue?

Noitham commented 3 months ago

A workaround is to delay the autofocus of the TextInput until after the screen is rendered. Please find an example below where it waits 500ms from when the screen is focused to focus on the TextInput. autoFocus on the TextInput itself is set to false.

// References for email input to use for focus
const inputRef = useRef(null);

// State to track whether the email input should be focused
const [shouldFocus, setShouldFocus] = useState(false);

// Wait for 500ms before setting the autoFocus prop to true
useEffect(() => {
  const timeout = setTimeout(() => {
    setShouldFocus(true);
  }, 500);

  return () => clearTimeout(timeout);
}, []);

// Set focus on the input element when shouldFocus is true
useEffect(() => {
  if (shouldFocus) {
    inputRef.current?.focus();
  }
}, [shouldFocus]);

Tried with

  useEffect(() => {
    const timer = setTimeout(() => {
      emailRef.current?.focus();
    }, 500);

    return () => clearTimeout(timer);
  }, []);

But still laggy

choi2021 commented 2 months ago

I experienced lag when trying to delay the autofocus of the TextInput. However, using the transitionEnd event resolved the issue for me.

I hope this solution works for you as well.

Here’s the code snippet that worked:

    useEffect(() => {
        const unsubscribe = navigation.addListener('transitionEnd', (e) => {
            inputRef.current?.focus();
        });

        return unsubscribe;
    }, [navigation]);