Fix: Status Bar Text Color Wrong on Dark Background in iOS

Fix: Status Bar Text Color Wrong on Dark Background in iOS

Written by

in

Quick answer: The status bar style is owned by a view controller, not by a global setting. Keep 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 UIViewControllerBasedStatusBarAppearance at YES and never toggle it per target or configuration.
  • Override childForStatusBarStyle in 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 — .default resolves 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.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.