At WWDC, apple talked about a new way to update your TabView’s and NavigationView’s appearance. At right around 4:30, they talk about these new UITabBarAppearance and UINavigationBarAppearance APIs. These provide an easy to update appearances and share common settings.

The old way: UIAppearance

Before we talk about the new APIs, I want to talk about what was available before this. Since iOS 5.0, developers had access to this UIAppearance API. This API still (kinda) works in SwiftUI, and is simple to use. I’ve seen quite a few tutorials out there for SwiftUI that set it up in the init function like so:

struct ContentView: View { init() { UINavigationBar.appearance().backgroundColor = .blue } var body: some View { NavigationView { NavigationLink(destination: Text("First detail")) { Text("Go to first detail") } } } }

Updating this inside the view’s init has some downsides. If you look at the UINavigationBar.appearance(), you’ll see that this is static. This means that whatever you change here will change on all NavigationViews. This causes issues whenever you have multiple NavigationViews, like in a TabView.

For this reason, if you are going to use UINavigationBar.appearance(), you should probably set appearances in the NavigationView’s .onAppear(). This is demonstrated with:

struct ContentView: View { var body: some View { TabView { FirstTabView() .tabItem { Text("First") } .tag(0) SecondTabView() .tabItem { Text("Second") } .tag(1) } } } struct FirstTabView: View { var body: some View { NavigationView { NavigationLink(destination: Text("First detail")) { Text("Go to first detail") } } .onAppear { UINavigationBar.appearance().backgroundColor = .green } } } struct SecondTabView: View { var body: some View { NavigationView { NavigationLink(destination: Text("Second detail")) { Text("Go to second detail") } } .onAppear { UINavigationBar.appearance().backgroundColor = .blue } } }

As you can see, this will only change the appearance when you show the NavigationView. Using onAppear can stop you from accidentally overwriting older appearances.

From here, you can update the navigation bar’s appearance. Some commonly updated appearances are:

titleTextAttributes

backgroundColor

tintColor / barTintColor

The New Way: UINavigationBarAppearance

Now that we’ve taken a look at how the old API works, let’s check out the new API. The WWDC video I linked to makes the API look very easy to use. The code they had for updating a NavigationBar’s appearance looks like:

let appearance = UINavigationBarAppearance() appearance.configureWithOpaqueBackground() appearance.titleTextAttributes = [.foregroundColor: myAppLabelColor] appearance.largeTitleTextAttributes = [.foregroundColor: myAppLabelColor] navigationBar.standardAppearance = appearance

What concerns me here is the navigationBar they are updating. This looks like the navigationbar from a UINavigationController, and we don’t have a way to access this in SwiftUI just yet. Looking through the documentation on NavigationView isn’t very fruitful, and the only resource I can find for this is this Stack Overflow post.

This means we 3 options for using this API:

Create your own NavigationView class (something similar to this custom TabView, but with UINavigationController) Extending the current UINavigationController (like in the Stack Overflow post) Wait for support from Apple (assuming they add support)

One assumption I’m going to make is that it will be easy to migrate from solutions 1 or 2 to solution 3 when Apple releases support. I’d expect Apple would let you pass in an appearance into the NavigationView, so this would be an easy migration.

Of the first 2 options, extending the UINavigationController is easier. The downside to this method is that since you can only extend a function once, these settings I put in here will end up being global settings. This won’t matter too much for my purposes.

The base code for extending the controller will look like the following code. Something import to note is I used ViewDidLoad instead of ViewDidAppear since ViewDidAppear will not apply the settings until you navigate to inside the NavigationLink’s destination view.

extension UINavigationController { override open func viewDidLoad() { super.viewDidLoad() let appearance = UINavigationBarAppearance() // update the appearance navigationBar.standardAppearance = appearance } }

The 3 NavigationBar Appearances

From here, we can start to get a little more creative. To start off, you have 3 different appearances you get with a NavigationBar. I’ll let the WWDC video define what each of these mean:

This was pretty unclear to me, so I wrote some code to test this. This has to be run on an iPhone 8/6s simulator since the compaceAppearance isn’t available on an iPhone X/Xs/11. loremIpsum is a local variable that is a few paragraphs of Lorem Ipsum text. I didn’t want to copy/paste it here since it’s pretty long.

struct ContentView: View { var body: some View { NavigationView { NavigationLink(destination: LoremIpsum()) { Text("Go to detail") } } } } struct LoremIpsum: View { var body: some View { ScrollView { Text(loremIpsum) } .navigationBarTitle("Go to detail") } } extension UINavigationController { override open func viewDidLoad() { super.viewDidLoad() let standardAppearance = UINavigationBarAppearance() standardAppearance.backgroundColor = .red let compactAppearance = UINavigationBarAppearance() compactAppearance.backgroundColor = .green let scrollEdgeAppearance = UINavigationBarAppearance() scrollEdgeAppearance.backgroundColor = .blue navigationBar.standardAppearance = standardAppearance navigationBar.compactAppearance = compactAppearance navigationBar.scrollEdgeAppearance = scrollEdgeAppearance } }

What Next

The rest of the new APIs look are straightforward. You get APIs to set the background, text appearance, button appearance and more. I don’t thinks there’s much value is diving deeper into this, so I’ll leave any extra exploration of this to you. I may re-visit this once there’s better support in SwiftUI for UINavigationBarAppearance.

What I find frustrating is that Apple released this new API without very good support in SwiftUI. I get that SwiftUI is new, but this would have been easy to add support for from the start. SwiftUI is so close but so far away from being ready.

Feel free to check out my YouTube video on this:

Thanks for reading.

– SchwiftyUI