SwiftUI: Status bar color

Is there a way to change the status bar to white for a SwiftUI view?

I'm probably missing something simple, but I can't seem to find a way to change the status bar to white in SwiftUI. So far I just see .statusBar(hidden: Bool).


The status bar text/tint/foreground color can be set to white by setting the View's .dark or .light mode color scheme using .preferredColorScheme(_ colorScheme: ColorScheme?).

The first view in your hierarchy that uses this method will take precedence.

For example:

var body: some View {
  ZStack { ... }
  .preferredColorScheme(.dark) // white tint on status bar
}
var body: some View {
  ZStack { ... }
  .preferredColorScheme(.light) // black tint on status bar
}

As in the comments linked to I edited this question here

But to answer this question and help people find the answer directly:

Swift 5 and SwiftUI

For SwiftUI create a new swift file called HostingController.swift

import SwiftUI

class HostingController<ContentView>: UIHostingController<ContentView> where ContentView : View {
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }
}

Then change the following lines of code in the SceneDelegate.swift

window.rootViewController = UIHostingController(rootView: ContentView())

to

window.rootViewController = HostingController(rootView: ContentView())

In info.plist, you can simply set

  • "Status bar style" to "Light Content"
  • "View controller-based status bar appearance" to NO

No need to change anything into your code...


Just add this to info.plist

<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>

tested on IOS 14, xcode 12


The existing answers cover the case where you want to just change the status bar color once (ex. use light content throughout your app), but if you want to do it programmatically then preference keys are a way to accomplish that.

The full example can be found below, but here is a description of what we're going to do:

  • Define a struct conforming to PreferenceKey, this will be used by Views to set their preferred status bar style
  • Create a subclass of UIHostingController that can detect preference changes and bridge them to the relevant UIKit code
  • Add an extension View to get an API that almost looks official

Preference Key Conformance

struct StatusBarStyleKey: PreferenceKey {
  static var defaultValue: UIStatusBarStyle = .default
  
  static func reduce(value: inout UIStatusBarStyle, nextValue: () -> UIStatusBarStyle) {
    value = nextValue()
  }
}

UIHostingController Subclass

class HostingController: UIHostingController<AnyView> {
  var statusBarStyle = UIStatusBarStyle.default

  //UIKit seems to observe changes on this, perhaps with KVO?
  //In any case, I found changing `statusBarStyle` was sufficient
  //and no other method calls were needed to force the status bar to update
  override var preferredStatusBarStyle: UIStatusBarStyle {
    statusBarStyle
  }

  init<T: View>(wrappedView: T) {
// This observer is necessary to break a dependency cycle - without it 
// onPreferenceChange would need to use self but self can't be used until 
// super.init is called, which can't be done until after onPreferenceChange is set up etc.
    let observer = Observer()

    let observedView = AnyView(wrappedView.onPreferenceChange(StatusBarStyleKey.self) { style in
      observer.value?.statusBarStyle = style
    })

    super.init(rootView: observedView)
    observer.value = self
  }

  private class Observer {
    weak var value: HostingController?
    init() {}
  }

  @available(*, unavailable) required init?(coder aDecoder: NSCoder) {
    // We aren't using storyboards, so this is unnecessary
    fatalError("Unavailable")
  }
}

View Extension

extension View {
  func statusBar(style: UIStatusBarStyle) -> some View {
    preference(key: StatusBarStyleKey.self, value: style)
  }
}

Usage

First, in your SceneDelegate you'll need to replace UIHostingController with your subclass:

//Previously: window.rootViewController = UIHostingController(rootView: rootView)
window.rootViewController = HostingController(wrappedView: rootView)

Any views can now use your extension to specify their preference:

VStack {
   Text("Something")
}.statusBar(style: .lightContent)

Notes

The solution of using a HostingController subclass to observe preference key changes was suggested in this answer to another question - I had previously used @EnvironmentObject which had a lot of downsides, preference keys seem much more suited to this problem.

Is this the right solution to this issue? I'm not sure. There are likely edge cases that this doesn't handle, for instance I haven't thoroughly tested to see what view gets priority if multiple views in the hierarchy specify a preference key. In my own usage, I have two mutually exclusive views that specify their preferred status bar style, so I haven't had to deal with this. So you may need to modify this to suit your needs (ex. maybe use a tuple to specify both a style and a priority, then have your HostingController check it's previous priority before overriding).