You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Since 1.3.0 (#199), large titles (navigationBar.prefersLargeTitles = true + navigationItem.largeTitleDisplayMode = .always) only work reliably on screens at the root of a navigation stack (e.g. tab roots). Screens pushed onto the stack render with a collapsed (small) title and only expand after the user pulls down to the top. First pushes sometimes appear correct; revisits (Turbo page-cache hits) fail deterministically.
Before 1.3.0 this didn't happen, because UIKit's subview-ordering heuristic usually failed to find the web view's scroll view at all — .always titles simply stayed large (the flip side was #169: titles never collapsed on scroll).
Environment
hotwire-native-ios 1.3.0 (also reproduces on current main)
Real app with HotwireTabBarController, custom HotwireNavigationController (prefersLargeTitles = true via Hotwire.config.defaultNavigationController), largeTitleDisplayMode set per-screen from path-configuration properties in viewWillAppear
Reproduced on iOS 26 device and iOS 27 simulator
Steps to reproduce
Set navigationBar.prefersLargeTitles = true in a custom navigation controller registered via Hotwire.config.defaultNavigationController.
In a HotwireWebViewController subclass, set navigationItem.largeTitleDisplayMode = .always in viewWillAppear for some screens.
From a small-title screen (e.g. a tab root with .never), tap a link to a large-title screen — it may still render correctly this first time.
Navigate back, then tap the same link again (Turbo page-cache hit).
The pushed screen renders with a collapsed title; pull down and it expands. Tab-root screens with .always are unaffected.
Findings (from instrumenting contentOffset / adjustedContentInset / bar height / transition state through the visitable lifecycle)
The web view activates before its view controller is pushed. At visitableDidActivateWebView time, navigationController is still nil — yet Fix TabBarMinimize and prefersLargeTitle behaviour #199's setContentScrollView(_:for:) (and the insertSubview(webView, at: 0) ordering) starts navigation-bar tracking at that moment.
Turbo scrolls the web view during visits. On a page-cache revisit, the snapshot render's scroll-to-top executes before/during the push and is computed against the outgoing page's geometry — e.g. contentOffset.y = -116 (small-bar top) instead of the incoming screen's expanded top (-168).
UIKit derives the incoming bar state from that stale tracked offset and locks the large title collapsed:
A slow first render can also collapse an initially-correct bar seconds after the push: the render's scroll reset lands at the small-bar top while the bar is expanded, UIKit reads it as an upward scroll and collapses:
A collapsed large-title bar resting at the top never re-expands without a user drag — which is why the broken state sticks.
Timing relative to the push transition decides the outcome, which is why the symptom looks random per-screen: cache-hit revisits and slow renders fail, everything else survives. Screens pushed from another large-title screen inherit a compatible offset and survive too.
Negative results (tried and verified not to work)
navigationController?.navigationBar.sizeToFit() after re-aligning the offset does not re-expand a collapsed bar resting at the top.
setContentScrollView(nil, for: .all) does not stop the tracking (and so can't prevent the stale-offset decision): the web view is the first subview, so UIKit's ordering heuristic keeps tracking it regardless.
The only input that changes the bar's expanded/collapsed state is tracked scroll-offset changes — the same mechanism that breaks it.
Expected behavior
A pushed screen with largeTitleDisplayMode = .always should render with the expanded large title, regardless of Turbo page-cache state or render timing — as tab-root screens already do.
Summary
Since 1.3.0 (#199), large titles (
navigationBar.prefersLargeTitles = true+navigationItem.largeTitleDisplayMode = .always) only work reliably on screens at the root of a navigation stack (e.g. tab roots). Screens pushed onto the stack render with a collapsed (small) title and only expand after the user pulls down to the top. First pushes sometimes appear correct; revisits (Turbo page-cache hits) fail deterministically.Before 1.3.0 this didn't happen, because UIKit's subview-ordering heuristic usually failed to find the web view's scroll view at all —
.alwaystitles simply stayed large (the flip side was #169: titles never collapsed on scroll).Environment
main)HotwireTabBarController, customHotwireNavigationController(prefersLargeTitles = trueviaHotwire.config.defaultNavigationController),largeTitleDisplayModeset per-screen from path-configuration properties inviewWillAppearSteps to reproduce
navigationBar.prefersLargeTitles = truein a custom navigation controller registered viaHotwire.config.defaultNavigationController.HotwireWebViewControllersubclass, setnavigationItem.largeTitleDisplayMode = .alwaysinviewWillAppearfor some screens..never), tap a link to a large-title screen — it may still render correctly this first time..alwaysare unaffected.Findings (from instrumenting
contentOffset/adjustedContentInset/ bar height / transition state through the visitable lifecycle)visitableDidActivateWebViewtime,navigationControlleris stillnil— yet Fix TabBarMinimize and prefersLargeTitle behaviour #199'ssetContentScrollView(_:for:)(and theinsertSubview(webView, at: 0)ordering) starts navigation-bar tracking at that moment.contentOffset.y = -116(small-bar top) instead of the incoming screen's expanded top (-168).Timing relative to the push transition decides the outcome, which is why the symptom looks random per-screen: cache-hit revisits and slow renders fail, everything else survives. Screens pushed from another large-title screen inherit a compatible offset and survive too.
Negative results (tried and verified not to work)
navigationController?.navigationBar.sizeToFit()after re-aligning the offset does not re-expand a collapsed bar resting at the top.setContentScrollView(nil, for: .all)does not stop the tracking (and so can't prevent the stale-offset decision): the web view is the first subview, so UIKit's ordering heuristic keeps tracking it regardless.The only input that changes the bar's expanded/collapsed state is tracked scroll-offset changes — the same mechanism that breaks it.
Expected behavior
A pushed screen with
largeTitleDisplayMode = .alwaysshould render with the expanded large title, regardless of Turbo page-cache state or render timing — as tab-root screens already do.Related: #169, #199.