Neil Macy

SwiftUI Map breaks UINavigationBar Appearance

In Running Track, I have a screen that shows details about a run, including a map of the route. I’ve been rewriting parts of the app in SwiftUI recently, and that screen has given me a headache!

Styling Globally With UINavigationBarAppearance

To have a consistent look across the app, I set UINavigationBarAppearance on launch. This gives me a standard background colour, tint colour and title font.

static func setNavigationBarAppearance() {
  let appearance = UINavigationBarAppearance()
  appearance.configureWithOpaqueBackground()
  appearance.backgroundColor = UIColor(.primaryColor)
  appearance.tintColor = UIColor(.darkBackgroundText)

  appearance.titleTextAttributes = [
    .foregroundColor: UIColor(.darkBackgroundText),
    .font: RTTextStyle.display4.font
  ]
  appearance.largeTitleTextAttributes = [
    .foregroundColor: UIColor(.darkBackgroundText),
    .font: RTTextStyle.display2.font
  ]

  UINavigationBar.appearance().standardAppearance = appearance
  UINavigationBar.appearance().scrollEdgeAppearance = appearance
}

There’s no equivalent of this in SwiftUI. No way to set navigation bar style globally. (I don’t know why not.)

The Problem in SwiftUI

For some reason, if you have a SwiftUI Map in your UI, it will override any global UINavigationBarAppearance you set. When the Map is in my View, it sets the navigation bar to the default style, a white background in light mode and a dark one in dark mode. (This applies whether you use NavigationStack or NavigationView.)

That means you can’t use UINavigationBarAppearance to style the navigation bar on that screen.

Solutions

There are two fixes for this, and both involve giving up on UINavigationBarAppearance and moving to the SwiftUI method of styling individual views explicitly: 1. Style this navigation bar explicitly, and leave the rest to UINavigationBarAppearance. 2. Get rid of UINavigationBarAppearance and explicitly style every view that lives in a NavigationStack.

You can style a navigation bar in SwiftUI with modifiers, but you have to apply them to each view individually:

someView
  .toolbarBackground(Color(.primary), for: .navigationBar)
  .toolbarBackground(.visible, for: .navigationBar) // otherwise it's hidden until you scroll
  .toolbarColorScheme(.dark, for: .navigationBar) // a hack to get white text and button tints

My Solution

I’m not able to go with the first solution, modifying only the View containing the map, because SwiftUI doesn’t give the same control over navigation bar styling that UIKit does.

For example, I change the text colour and font in my UINavigationBarAppearance setup. But can’t change these in SwiftUI’s navigation bar. So I can’t have an exception just for the screen containing the map, without it looking different to the rest of the app. I need to completely change my navigation bar style.

So I’ve had to go nuclear and get rid of UINavigationBarAppearance.

I created a modifier to reduce some of the repetition, but I have to set it on every View that's presented in a NavigationStack:

struct NavigationBarStyle: ViewModifier {
  func body(content: Content) -> some View {
    content
      .toolbarBackground(Color(.primary), for: .navigationBar)

      // otherwise it's hidden until you scroll
      .toolbarBackground(.visible, for: .navigationBar)

      // a hack to get white text and button tints
      .toolbarColorScheme(.dark, for: .navigationBar)
  }
}

extension View {
  func navigationBarStyle() -> some View {
    self.modifier(NavigationBarStyle())
  }
}

If anyone has a better solution to this, let me know!

(Note: This issue is specific to rendering a Map, so if you're not using a Map in your UI you may not need to worry about this.)




If you liked this article, please consider buying me a coffee to support my writing.

Published on 18 June 2025