diff --git a/ios/RNSScreenStackHeaderConfig.mm b/ios/RNSScreenStackHeaderConfig.mm index e7947366fd..987ed0fbb0 100644 --- a/ios/RNSScreenStackHeaderConfig.mm +++ b/ios/RNSScreenStackHeaderConfig.mm @@ -264,7 +264,13 @@ - (void)updateHeaderStateInShadowTreeInContextOfNavigationBar:(nullable UINaviga - (NSDirectionalEdgeInsets)computeEdgeInsetsOfNavigationBar:(nonnull UINavigationBar *)navigationBar { NSDirectionalEdgeInsets navBarMargins = [navigationBar directionalLayoutMargins]; - NSDirectionalEdgeInsets navBarContentMargins = [navigationBar.rnscreens_findContentView directionalLayoutMargins]; + NSDirectionalEdgeInsets navBarContentMargins; + if (@available(iOS 26.0, *)) { + UIView *titleControl = [UINavigationBar findTitleControlInView:navigationBar]; + navBarContentMargins = [navigationBar rnscreens_computeTotalEdgeInsetsForView:titleControl]; + } else { + navBarContentMargins = [navigationBar.rnscreens_findContentView directionalLayoutMargins]; + } BOOL isDisplayingBackButton = [self shouldBackButtonBeVisibleInNavigationBar:navigationBar]; diff --git a/ios/utils/UINavigationBar+RNSUtility.h b/ios/utils/UINavigationBar+RNSUtility.h index 24af95fc29..b0d6003bf8 100644 --- a/ios/utils/UINavigationBar+RNSUtility.h +++ b/ios/utils/UINavigationBar+RNSUtility.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN * * Tested to work reliably on iOS 18.0, 17.5, 15.5. * - * @returns `_UINavigationBarContentView` view mounted directly under the navigation bar itself + * @return `_UINavigationBarContentView` view mounted directly under the navigation bar itself */ - (nullable UIView *)rnscreens_findContentView; @@ -27,11 +27,47 @@ NS_ASSUME_NONNULL_BEGIN * * Tested to work reliably on iOS 18.0, 17.5, 15.5. * - * @returns `_UIButtonBarButton` view, if present and mounted in anticipated place; + * @return `_UIButtonBarButton` view, if present and mounted in anticipated place; * if the back button is not present, this method returns `nil`. */ - (nullable UIView *)rnscreens_findBackButtonWrapperView; +/** + * @brief Responsible for calculating the cumulative directional layout margins the will be applied to the title in the + * header. + * + * This method traverses the superview hierarchy, starting from the parent of the specified source view, + * and accumulates all `directionalLayoutMargins` until it reaches the `UINavigationBar`. + * The resulting insets represent the total margin space between the source view and its navigation container. + * + * Tested to work reliably on iOS 26.0, 18.5. + * + * @param sourceView The UIView from which to begin traversing up towards the navigation bar. + * @return `NSDirectionalEdgeInsets` containing the combined layout margins from the title control + * up to its ancestor navigation bar. Excluding the layout margins of both the: + * - source view - because we're positioning relatively to this view + * - `UINavigationBar` - because we're calculating it inside computeEdgeInsetsOfNavigationBar explicitly + */ +- (NSDirectionalEdgeInsets)rnscreens_computeTotalEdgeInsetsForView:(nullable UIView *)sourceView; + +/** + * @brief Responsible for searching the view hierarchy to locate the `_UINavigationBarTitleControl` view. + * + * This method traverses a subtree from given view, to find `_UINavigationBarTitleControl`. + * `_UINavigationBarTitleControl` is a private API. Therefore, this method relies on the + * hardcoded class name which may change at any time. + * + * The method performs a DFS starting from the provided view. + * Once a matching title control is found, it is returned immediately. If not found, + * the method returns nil. + * + * Tested to work reliably on iOS 26.0, 18.5. + * + * @param view The root UIView from which to begin searching the view hierarchy. + * @return `UIView` instance representing the `_UINavigationBarTitleControl` or `nil`. + */ ++ (UIView *)findTitleControlInView:(UIView *)view; + @end NS_ASSUME_NONNULL_END diff --git a/ios/utils/UINavigationBar+RNSUtility.mm b/ios/utils/UINavigationBar+RNSUtility.mm index bc3d7c70a6..60463c777a 100644 --- a/ios/utils/UINavigationBar+RNSUtility.mm +++ b/ios/utils/UINavigationBar+RNSUtility.mm @@ -1,5 +1,7 @@ #import "UINavigationBar+RNSUtility.h" +#import + @implementation UINavigationBar (RNSUtility) - (nullable UIView *)rnscreens_findContentView @@ -83,4 +85,43 @@ + (Class)rnscreens_getContentViewRuntimeClass return NSClassFromString(@"_UINavigationBarContentView"); // Sampled from iOS 17.5 (iPhone 15 Pro) } +- (NSDirectionalEdgeInsets)rnscreens_computeTotalEdgeInsetsForView:(nullable UIView *)sourceView +{ + NSDirectionalEdgeInsets totalMargins = NSDirectionalEdgeInsetsMake(0, 0, 0, 0); + + if (sourceView == nil) { + RCTLogWarn(@"[RNScreens] _UINavigationBarTitleControl not found under the UINavigationBar hierarchy"); + return totalMargins; + } + + UIView *currentView = sourceView.superview; + while (currentView != nil && currentView != self) { + NSDirectionalEdgeInsets margins = currentView.directionalLayoutMargins; + totalMargins.top += margins.top; + totalMargins.leading += margins.leading; + totalMargins.bottom += margins.bottom; + totalMargins.trailing += margins.trailing; + currentView = currentView.superview; + } + return totalMargins; +} + ++ (UIView *)findTitleControlInView:(UIView *)view +{ + static Class UINavBarTitleControlClass = NSClassFromString(@"_UINavigationBarTitleControl"); + + if ([view isKindOfClass:UINavBarTitleControlClass]) { + return view; + } + + for (UIView *subview in view.subviews) { + UIView *result = [UINavigationBar findTitleControlInView:subview]; + if (result) { + return result; + } + } + + return nil; +} + @end