IjzerenHein / react-navigation-shared-element

React Navigation bindings for react-native-shared-element 💫
https://github.com/IjzerenHein/react-native-shared-element
MIT License
1.27k stars 124 forks source link

[iOS] Offset when using SafeAreaView #187

Open BeeMargarida opened 3 years ago

BeeMargarida commented 3 years ago

First of all, thank you for this amazing library!

I found an issue in iOS when using SafeAreaView (both from react-native and react-native-safe-area-context) with SharedElements. Without the SafeAreaView it works well. When making the animation, there is a little "jump", where the final place of the animation is off by some offset.

The code can be found and it's runnable in https://github.com/BeeMargarida/react-navigation-shared-element, it's the last example (sorry for the quality of the code, it was a testing scenario made really fast).

Code ``` import { NavigationContainer } from "@react-navigation/native"; import { createStackNavigator } from "@react-navigation/stack"; import React from "react"; import { Button, View } from "react-native"; import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; import { SharedElement, createSharedElementStackNavigator, } from "react-navigation-shared-element"; const Stack = createStackNavigator(); const SharedElementStack = createSharedElementStackNavigator(); const forFade = ({ current, closing }) => ({ cardStyle: { opacity: current.progress, }, }); const Screen1 = (props: any) => { return (

https://user-images.githubusercontent.com/25725586/130613627-91929813-9b74-491c-a59a-3220db436186.mp4

IjzerenHein commented 3 years ago

Hi, and thanks for sharing the elaborate example, that was very useful. I've added a test-case to the example app and was able to reproduce similar problems. Both iOS and Android seem to be experiencing problems. I've had a closer look at the native iOS SafeAreaView implementation in react-native-safe-area-context and I sort of see what's going on. The problem is in the fact that <SafeAreaView> needs an extra pass to the React Native UIManager and Yoga to set the padding on the view. Both the iOS and Android implementations seem to do this in a similar way. This causes react-native-shared-element to measure the size and position of the element before <SafeAreaView> has applied its padding, causing the shift that you see.

I'm not really sure on how to fix this tbh. react-native-shared-element measures your view as fast as possible (to prevent and flickering/ghost elements). For the best results you should construct your views in such a way that they don't require and re-layouting. And unfortunately <SafeAreaView> causes such re-layouting, even though it is performed at the native level.

BeeMargarida commented 3 years ago

Hi, and thanks for sharing the elaborate example, that was very useful. I've added a test-case to the example app and was able to reproduce similar problems. Both iOS and Android seem to be experiencing problems. I've had a closer look at the native iOS SafeAreaView implementation in react-native-safe-area-context and I sort of see what's going on. The problem is in the fact that <SafeAreaView> needs an extra pass to the React Native UIManager and Yoga to set the padding on the view. Both the iOS and Android implementations seem to do this in a similar way. This causes react-native-shared-element to measure the size and position of the element before <SafeAreaView> has applied its padding, causing the shift that you see.

I'm not really sure on how to fix this tbh. react-native-shared-element measures your view as fast as possible (to prevent and flickering/ghost elements). For the best results you should construct your views in such a way that they don't require and re-layouting. And unfortunately <SafeAreaView> causes such re-layouting, even though it is performed at the native level.

Thank you for you very fast response!

Just to get your opinion, would implement the safe area logic locally (use the insets initialWindowMetrics from "react-native-safe-area-context" as padding for the view of each screen) solve this problem?

IjzerenHein commented 3 years ago

Not sure, but I would definitely try 👍

IjzerenHein commented 3 years ago

Alternatively, you could try caching the insets so that they are immediately available when your view is rendered for the first time.

BeeMargarida commented 3 years ago

Alternatively, you could try caching the insets so that they are immediately available when your view is rendered for the first time.

It seems caching does not seem to work as well. Either way, thank you!

aggr2150 commented 2 years ago

https://github.com/th3rdwave/react-native-safe-area-context#usesafeareainsets works for me

import {useSafeAreaInsets} from "react-native-safe-area-context";

const insets = useSafeAreaInsets();
const Screen = () => {
    return (
        <View style={{
            flex: 1,
            paddingTop: insets.top,
            paddingLeft: insets.left,
            paddingRight: insets.right,
            paddingBottom: insets.bottom
        }}>
            <SharedElement id="itemId">
            </SharedElement>
        </View>
    );
};
edvard-bjarnason commented 1 year ago

The navigation option headerShown:false makes the offset and jump bigger when using <SafeAreaView>

Creating a view as suggested above with padding solves the issue for me :)

GhostWalker562 commented 9 months ago

Having this issue as well, would be great to see a solution. Specifically with the KeyboardAvoidingView