Skip to content

Conversation

@kligarski
Copy link
Contributor

@kligarski kligarski commented Dec 4, 2025

Description

Fixes alignment of custom view inside bar button item on iOS 26.

Note

Please note that this will also change positioning of bar button items with hidesSharedBackground: true on iOS 26.

before after
before3449.mov
after3449.mov

Closes #2990.

Explanation

Starting from iOS 26, custom view inside UIBarButtonItem is stretched to at least 36 width. This causes our RNSScreenStackHeaderSubview to stretch if its width is smaller than 36, leaving subviews in the same origin (for one subview, it's usually (0,0); that's why it is aligned to the left, visible in "before" recording).

In order to mitigate this, we add wrapperView which will be stretched by UIKit to required minimum size. RNSScreenStackHeaderSubview will be centered inside of it. Additional constraints and properties needed to be added to make sure that wrapperView is not stretched to all available space in UINavigationBar (this would happen in headerRight if we used only wrapperView.width >= RNSScreenStackHeaderSubview.width constraint):

  • wrapperView.width == RNSScreenStackHeaderSubview.width - makes sure that wrapper matches content. By using lower priority than default, we allow UIKit to "take over" with its minimum width, while maintaining "fit-content"/"content hugging" behavior,
  • [self setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal] - prevents stretching to all available width in UINavigationBar.

RNSScreenStackHeaderSubview needs to use intrinsicContentSize in order to bridge auto-layout centering and frame size from Yoga.

Changes

  • on iOS 26, add wrapper view between UIBarButtonItem and RNSScreenStackHeaderSubview
  • center RNSScreenStackHeaderSubview inside wrapperView while maintaining correct sizing via auto layout
    • use intrinsicContentSize to bridge between auto-layout and frames calculated by Yoga
  • fix frame passed to shadow tree (now, RNSScreenStackHeaderSubview can have non-zero offset (which was not the case previously); without change to self.bounds, this offset would be accounted for 2 times when using [self convertRect:frame toView:ancestorView])

Test code and steps to reproduce

Run Test3446.

Checklist

  • Included code example that can be used to test this change
  • Ensured that CI passes

@kligarski kligarski force-pushed the @kligarski/center-view-inside-bar-button-item-ios-26 branch from f8af1a1 to a61fbc5 Compare December 4, 2025 10:17
@kligarski kligarski force-pushed the @kligarski/center-view-inside-bar-button-item-ios-26 branch from a61fbc5 to cad5e26 Compare December 4, 2025 10:20
Comment on lines +249 to +251
NSLayoutConstraint *heightEqual = [wrapperView.heightAnchor constraintEqualToAnchor:self.heightAnchor];
heightEqual.priority = UILayoutPriorityDefaultHigh;
heightEqual.active = YES;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, we can remove height constraints and allow wrapperView to grow in height. This would make the entire liquid glass button interactive (currently, area inside liquid glass button but above and below custom view is not interactive as wrapperView is stretched by UIKit only in horizontal axis).

@kligarski kligarski marked this pull request as ready for review December 4, 2025 11:49
Comment on lines +182 to +183
_layoutMetrics = layoutMetrics;
[self invalidateIntrinsicContentSize];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we may consider calling it only if layoutMetrics have changed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

3 participants