ReviewOS

craft-native/craft

No way to give a native screen's navigation bar an opaque background

#360
Closed glennmichael123 opened this 9 days ago ·
9 days ago

What is missing

There is no way to give a native screen's navigation bar an opaque background. CraftNativeScreenController sets prefersLargeTitles and nothing else, so UIKit's default appearance applies and the bar keeps its material.

On iOS 26 that material is transparent enough that scrolled content is legible through it. For a plain list that is fine and intended. For a screen whose own header sticks under the bar — a week strip, a filter row, a segmented control — it is not: the sticky header is opaque, so you get a solid band directly below a cloudy one, with body text showing through the top of it and a visible seam between the two.

backgroundColor in setOptions does not help. It sets view.backgroundColor, which is behind the scrolling content; the bar blurs the content, not the screen colour.

What would fix it

A per-screen option that fills the bar with the screen's own colour:

if let solid = options["navigationBarSolid"] as? Bool {
    navigationBarSolid = solid
    applyNavigationBarFill()
}

private func applyNavigationBarFill() {
    guard navigationBarSolid else {
        navigationItem.standardAppearance = nil
        navigationItem.scrollEdgeAppearance = nil
        navigationItem.compactAppearance = nil
        navigationItem.compactScrollEdgeAppearance = nil
        return
    }
    let appearance = UINavigationBarAppearance()
    appearance.configureWithOpaqueBackground()
    appearance.backgroundColor = screenBackgroundColor ?? config.resolvedBackgroundColor
    appearance.shadowColor = .clear
    navigationItem.standardAppearance = appearance
    navigationItem.scrollEdgeAppearance = appearance
    navigationItem.compactAppearance = appearance
    navigationItem.compactScrollEdgeAppearance = appearance
}

Three things worth keeping if you take this shape:

  • resolvedBackgroundColor rather than a hex. It is already a dynamic UIColor, so the bar repaints on a Dark Mode switch without anything being set again, and it matches whatever the screen is actually drawn on.
  • All four appearances. Otherwise the bar changes as you scroll, which is the seam reappearing in a different place.
  • shadowColor = .clear. The hairline is part of what makes it read as two surfaces.

Opt-in, defaulting to today's behaviour, so no existing app changes.

Built and ran it against an all-native five-tab app on iPhone 17 Pro (iOS 26), craft-native@0.0.187: with it off, a card is legible through the bar at the top of the screen; with it on, the content is clipped cleanly at the bar's edge and the bar and the sticky header below it read as one surface, in both light and dark. Happy to open a PR.

9 days ago

Resolved on main by 7de71de (feat(ios): support solid native navigation bars). The opt-in navigationBarSolid path configures all four UIKit appearance slots, follows dynamic screen background traits, and preserves the translucent default. PR #361 adds the remaining title-mode fixture/docs coverage; the implementation itself is already on main and unreleased.

Sign in to comment on this issue.