Skip to content

[iOS] Header buttons are not centered in iOS 26 (w/ some investigation done on my end)Β #2990

@Armster15

Description

@Armster15

Description

On iOS 26 with the new Liquid Glass, header buttons are not vertically centered.

Image

Steps to reproduce

🐞 Minimal reproducible:

  1. Build a RN app with Xcode 26 on iOS 26

  2. Have code like so:

    import { Stack } from "expo-router";
    import { Text, TouchableOpacity } from "react-native";
      
    export default function RootLayout() {
      return (
        <Stack
          screenOptions={{
            headerRight: () => (
              <TouchableOpacity style={{ backgroundColor: "blue" }}>
                <Text>Hello</Text>
              </TouchableOpacity>
            ),
          }}
        />
      );
    }

    I'm using Expo Router, but regardless the main thing is to have some component other than React Native's default Button export (which seems to work for some reason) as the component in headerRight.

  3. See the following difference on iOS 18 and iOS 26

🧐 My Investigations

I inspected the view hierarchies of both iOS 18 and 26 and discovered the following:

iOS 18:
Image

iOS 26:

Image

To me I thought a quick and dirty fix would be to just forcefully center the subviews inside the RNSScreenStackHeaderSubview view. So that's what I did: I modified RNSScreenStackHeaderSubview.mm, more specifically the layoutSubviews function like so:

Before:

- (void)layoutSubviews
{
  [super layoutSubviews];
  [self updateShadowStateInContextOfAncestorView:[self findNavigationBar]];
}

After:

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (@available(iOS 26.0, *)) {
      if (self.subviews.count > 0) {
        UIView *child = self.subviews[0];
        CGRect frame = child.frame;
        CGFloat x = (self.bounds.size.width - frame.size.width) / 2.0;
        CGFloat y = (self.bounds.size.height - frame.size.height) / 2.0;
        child.frame = CGRectMake(x, y, frame.size.width, frame.size.height);
      }
    }
    
    [self updateShadowStateInContextOfAncestorView:[self findNavigationBar]];
}

...and that seemed to fix it πŸ‘€ πŸ‘€

Image

Now this is not a suitable fix by any means as this is very hacky, but I hope that these findings can help with diagnosing with the issue is.

If a non-maintainer is struggling with this, feel free to use patch-package to implement this dirty hack. The full path location is node_modules/react-native-screens/ios/RNSScreenStackHeaderSubview.mm. However, I'd like to emphasize that this fix is very hacky. Use with caution!

Thank you!

Snack or a link to a repository

https://github.com/Armster15/rnscreens-ios-26-header-reproducible

Screens version

4.11.1

React Native version

0.79.5

Platforms

iOS

JavaScript runtime

None

Workflow

None

Architecture

None

Build type

None

Device

None

Device model

No response

Acknowledgements

Yes

Metadata

Metadata

Assignees

Labels

Missing infoThe user didn't precise the problem enoughPlatform: iOSThis issue is specific to iOSRepro providedA reproduction with a snack or repo is provided

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions