UIViewControllerBasedStatusBarAppearance set to YES in Info.plist, then override preferredStatusBarStyle to return .lightContent on the controller that owns the bar — and make container controllers forward the question via childForStatusBarStyle. In SwiftUI, subclass UIHostingController and override the same property.Why this happens
Since iOS 7 the status bar is drawn on top of your content, and its text color is decided by the view controller that currently owns the status bar appearance. There is no app-wide “status bar color” setting anymore. If nothing tells the system otherwise, you get the default style, which is dark text — invisible against a dark background except for the battery glyph.
Two things usually break the override. Either UIViewControllerBasedStatusBarAppearance is set to NO in Info.plist, which makes the system ignore every preferredStatusBarStyle in your app, or the controller the system actually asks is a container — UINavigationController, UITabBarController, or UIHostingController — and it answers for itself instead of forwarding to your screen.
SwiftUI adds a third wrinkle: ContentView is not a view controller, so there is no preferredStatusBarStyle to override until you supply one.
How to fix it
1. Check the Info.plist key first
UIViewControllerBasedStatusBarAppearance must be YES. It is the default, but many projects set it to NO years ago following the old global-API advice, and that silently disables everything below.
In the target’s Info tab, add View controller-based status bar appearance and set it to YES. If you edit the plist as XML:
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
2. Override preferredStatusBarStyle in UIKit
On the view controller whose background is dark:
final class DarkBackgroundViewController: UIViewController {
override var preferredStatusBarStyle: UIStatusBarStyle {
.lightContent
}
override func viewDidLoad() {
super.viewDidLoad()
setNeedsStatusBarAppearanceUpdate()
}
}
setNeedsStatusBarAppearanceUpdate() matters when the style changes at runtime, for example after a theme switch. Call it whenever the value the property returns changes, otherwise the bar keeps the old color until the next transition.
For the reverse case — a light background with white status bar text — return .darkContent (iOS 13 and later).
3. Make container controllers forward the question
UINavigationController answers preferredStatusBarStyle itself, so an override on the pushed controller never runs. Subclass it and forward:
final class StatusBarNavigationController: UINavigationController {
override var childForStatusBarStyle: UIViewController? {
topViewController
}
}
Do the same for UITabBarController, returning selectedViewController. If the controller comes from a storyboard, set the custom class in the identity inspector rather than creating it in code.
4. SwiftUI: wrap the root view in a hosting controller
SwiftUI has no status bar style modifier, so give the system a view controller that has an opinion:
import SwiftUI
import UIKit
final class HostingController<Content: View>: UIHostingController<Content> {
private let statusBarStyle: UIStatusBarStyle
init(rootView: Content, statusBarStyle: UIStatusBarStyle = .lightContent) {
self.statusBarStyle = statusBarStyle
super.init(rootView: rootView)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var preferredStatusBarStyle: UIStatusBarStyle {
statusBarStyle
}
}
Then install it as the window’s root instead of a plain UIHostingController:
let controller = HostingController(rootView: ContentView())
window?.rootViewController = controller
window?.makeKeyAndVisible()
This works when you own the window — the UIKit app lifecycle with a scene delegate, or a SwiftUI app that uses UIApplicationDelegateAdaptor. If your app uses the pure SwiftUI lifecycle with WindowGroup, you cannot swap the root controller this way; use the option below instead.
5. The blunt SwiftUI option: preferredColorScheme
WindowGroup {
ContentView()
.preferredColorScheme(.dark)
}
Forcing dark mode makes the status bar text light. Only use this when the whole interface is dark anyway — otherwise you are repainting every color in the app to fix one label.
6. Navigation bars and toolbars
If the screen has a navigation bar, .toolbarColorScheme(.dark, for: .navigationBar) (iOS 16 and later) renders that bar in dark mode, which also flips the status bar text while the bar is on screen. It is a per-view modifier, so it does not leak into other screens.
7. Do not use the old global API
UIApplication.shared.statusBarStyle and setStatusBarStyle(_:animated:) were deprecated in iOS 9 and are unavailable in current SDKs. Any recipe that tells you to set UIViewControllerBasedStatusBarAppearance to NO and call them is from the iOS 7 era and will not compile today.
How to prevent it
- Keep
UIViewControllerBasedStatusBarAppearanceatYESand never toggle it per target or configuration. - Override
childForStatusBarStylein every custom container controller you write, including navigation and tab subclasses. - Call
setNeedsStatusBarAppearanceUpdate()after any runtime change to the style. - Test the screen in both light and dark appearance —
.defaultresolves differently in each. - Remember that a full-screen presentation owns the status bar while it is visible.
FAQ
Why does my preferredStatusBarStyle override do nothing inside a UINavigationController?
The navigation controller is the view controller the system asks, and it answers for itself. Override childForStatusBarStyle to return topViewController, or set the navigation controller’s custom class in the storyboard.
Can I change the status bar text color in SwiftUI without UIKit?
Not directly — SwiftUI has no status bar style modifier. .preferredColorScheme(.dark) flips the status bar to light text but also forces dark mode across the app. For a per-screen style, subclass UIHostingController and override preferredStatusBarStyle.
Is UIApplication.shared.statusBarStyle still available?
No. setStatusBarStyle(_:animated:) was deprecated in iOS 9 and the property is unavailable in current SDKs. Use preferredStatusBarStyle on the view controller that owns the status bar.
Why is the status bar still dark on my presented sheet?
A full-screen presentation captures the status bar appearance, so the presented controller’s preferredStatusBarStyle wins while it is visible. Set modalPresentationCapturesStatusBarAppearance if the presenting controller needs to keep control.
Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.
