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

insets.top switching to 0 during iPad orientation change #485

Open
Gregoirevda opened this issue Mar 27, 2024 · 11 comments
Open

insets.top switching to 0 during iPad orientation change #485

Gregoirevda opened this issue Mar 27, 2024 · 11 comments
Labels
help wanted Extra attention is needed Platform: iOS

Comments

@Gregoirevda
Copy link

Gregoirevda commented Mar 27, 2024

On iPad, insets.top changes during an orientation change from X to 0 and back to X, causing the app to re-render and recalculate layouts back and forth.

This only happens when going from landscape to portrait.
I've opened a related issue on RN-navigation: react-navigation/react-navigation#11915

EDIT:
insets.top changes from landscape to portrait to 0
insets.bottom changes from portrait to landscape to 0

@jacobp100
Copy link
Collaborator

Does it happen with just this library - or is it only with react navigation?

@Gregoirevda
Copy link
Author

@jacobp100
I've tried and happens just with react-native-safe-area-context

import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';

export default function App() {
  return (
    <SafeAreaProvider>
      <Kid />
    </SafeAreaProvider>
  );
}

function Kid() {
  const insets = useSafeAreaInsets();
  console.log({ insets });

  return null;
}
// Opening the app on iPad in portrait mode
[2]  LOG  {"insets": {"bottom": 20, "left": 0, "right": 0, "top": 74}}

// Changing orientation to landscape (switches to 0 and back to 20)
[2]  LOG  {"insets": {"bottom": 0, "left": 0, "right": 0, "top": 74}}
[2]  LOG  {"insets": {"bottom": 20, "left": 0, "right": 0, "top": 74}}
// End orientation change

// Changing orientation back to portrait
[2]  LOG  {"insets": {"bottom": 0, "left": 0, "right": 0, "top": 74}}
[2]  LOG  {"insets": {"bottom": 0, "left": 0, "right": 0, "top": 0}}
[2]  LOG  {"insets": {"bottom": 0, "left": 0, "right": 0, "top": 74}}
[2]  LOG  {"insets": {"bottom": 20, "left": 0, "right": 0, "top": 74}}
// End orientation change

As all iOS apps with a blurred header put <ScrollView contentContainerStyle={{paddingTop: insets.top}}
and all apps having a blurred bottom tab navigation have <ScrollView contentContainerStyle={{paddingBottom: insets.bottom}}
This causes the ScrollView to lag a lot when changing orientation

@jacobp100
Copy link
Collaborator

For the lag, try setting contentInset and scrollIndicatorInsets rather than padding - you'll be able to skip a layout update. You could also put a SafeAreaView with the insets inside the scrollview, as that component can shortcut a few processes, so can do the updates faster than your JS implementation can.

I'm not too sure why you get those insets - but presumably, iOS is giving them. If you're comfortable with Xcode, it would useful to know if those are coming from UIKit, and how far apart they are. I know react native can coalesce some events that are really close (like scroll events) - it's opt-in, but it might be a good workaround

@jacobp100 jacobp100 added help wanted Extra attention is needed Platform: iOS labels Mar 28, 2024
@Gregoirevda
Copy link
Author

I've replaced

- (void)safeAreaInsetsDidChange
{
  [self invalidateSafeAreaInsets];
}

by

- (void) viewSafeAreaInsetsDidChange
{
    [self invalidateSafeAreaInsets];
}

in RNCSafeAeraProvider.mm

and that does the trick of not switching to 0 during orientation change.

I've also debugged the app more, and it turns out I have a useEffect, depending on a useCallback, depending on useBottomTabBarOffset (react-navigation-bottom-tabs) depending on useSafeAreaInsets

// useBottomTabBarOffset in RN-bottom-tabs
const getPaddingBottom = (insets: EdgeInsets) =>
  Math.max(insets.bottom - Platform.select({ ios: 4, default: 0 }), 0);

@Gregoirevda
Copy link
Author

lol viewSafeAreaInsetsDidChange is never called so indeed it solves the problem, but insets aren't sent anymore.

this solves the problem

  if (safeAreaInsets.top < 1.0 || safeAreaInsets.bottom < 1.0 || ( _initialInsetsSent &&
      UIEdgeInsetsEqualToEdgeInsetsWithThreshold(safeAreaInsets, _currentSafeAreaInsets, 1.0 / RCTScreenScale()) &&
      CGRectEqualToRect(frame, _currentFrame))) {
    return;
  }

Maybe this should be improved

  // This gets called before the view size is set by react-native so
  // make sure to wait so we don't set wrong insets to JS.
  if (CGSizeEqualToSize(self.frame.size, CGSizeZero)) {
    return;
  }

@jacobp100
Copy link
Collaborator

You want to look in RNCSafeAreaProvider - that receives the insets from iOS, and passes them down to all the safe area views - see this issue for more info #92

I think the check proposed isn't safe, because the insets can be zero in some circumstances. It would be useful to get information about the timing between the two events. Are they almost instantly after each other?

@Gregoirevda
Copy link
Author

@jacobp100 code I was referring to is indeed in RNCSafeAreaProvider.
Yes, the events are coming instantly one after another during orientation transition

@jacobp100
Copy link
Collaborator

  • (void) viewSafeAreaInsetsDidChange

Is that called? That's a UIViewContoller method - and this is a UIView

@Gregoirevda
Copy link
Author

lol viewSafeAreaInsetsDidChange is never called so indeed it solves the problem, but insets aren't sent anymore.

UIViewContoller indeed

@Gregoirevda
Copy link
Author

@jacobp100 What would be a good way to batch onInsetsChange?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed Platform: iOS
Projects
None yet
Development

No branches or pull requests

2 participants