Reading time: 2 min

This tutorial shows how to style a navigation bar in SwiftUI - changing its background color, text color, as well as styling the status bar.

The end result looks like this:

This component is available as a Swift Package in this repo.

The gist of the work is in using a ViewModifier to provide a customized UINavigationBarAppearance:

struct NavigationBarModifier: ViewModifier {
  var backgroundColor: UIColor
  var textColor: UIColor

  init(backgroundColor: UIColor, textColor: UIColor) {
    self.backgroundColor = backgroundColor
    self.textColor = textColor
    let coloredAppearance = UINavigationBarAppearance()
    coloredAppearance.configureWithTransparentBackground()
    coloredAppearance.backgroundColor = .clear
    coloredAppearance.titleTextAttributes = [.foregroundColor: textColor]
    coloredAppearance.largeTitleTextAttributes = [.foregroundColor: textColor]

    UINavigationBar.appearance().standardAppearance = coloredAppearance
    UINavigationBar.appearance().compactAppearance = coloredAppearance
    UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
    UINavigationBar.appearance().tintColor = textColor
  }

  func body(content: Content) -> some View {
    ZStack{
       content
        VStack {
          GeometryReader { geometry in
             Color(self.backgroundColor)
                .frame(height: geometry.safeAreaInsets.top)
                .edgesIgnoringSafeArea(.top)
              Spacer()
          }
        }
     }
  }
}

Then, add this handy extension to apply the modifier to any View:

extension View {
  func navigationBarColor(_ backgroundColor: UIColor, textColor: UIColor) -> some View {
    self.modifier(NavigationBarModifier(backgroundColor: backgroundColor, textColor: textColor))
  }
}

Since your app is likely going to have a consistent navigation bar coloring, you can add another View extension to make things even easier:

extension View {
  var blueNavigation: some View {
    self.navigationBarColor(UIColor.themeBlue, textColor: UIColor.white)
  }
}

With that set, let's override the status bar style as well. First, add this custom UIHostingController:

class HostingController<Content> : UIHostingController<Content> where Content : View {
  @objc override dynamic open var preferredStatusBarStyle: UIStatusBarStyle {
     return .lightContent
  }
}

Then, in your SceneDelegate.swift, replace UIHostingController with HostingController:

// ...
    if let windowScene = scene as? UIWindowScene {
      let window = UIWindow(windowScene: windowScene)
      window.rootViewController = HostingController(rootView: contentView) // NOTICE THE CHANGE
      self.window = window
      window.makeKeyAndVisible()
    }
//...

That's it! You can now easily apply your navigation styling in any of your views:

var body: some View {
  NavigationView {
    VStack {
      Text("Hello World!")
    }.blueNavigation() // HERE
  }
}

Next Post Previous Post