Navigation bar styling in SwiftUI
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
}
}