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 byView
s 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).