Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
SwiftUI Change Status Bar Color with UIWindow
//
// ContentView.swift
// StatusBarTest
//
// Created by hiroki on 2021/02/11.
//
import SwiftUI
struct ContentView: View {
@StateObject var statusBarConfigurator = StatusBarConfigurator()
var body: some View {
VStack(spacing: 30) {
Text("Status Bar Color Test")
.font(.title)
Button("Black") {
statusBarConfigurator.statusBarStyle = .darkContent
}
Button("White") {
statusBarConfigurator.statusBarStyle = .lightContent
}
}
.prepareStatusBarConfigurator(statusBarConfigurator) // UIWindowの用意
.frame(maxWidth: .infinity, maxHeight: .infinity) // 背景色をステータスバーまで広げるため
.background(Color.gray.ignoresSafeArea()) // ステータスバーの色をわかりやすくするために背景をグレーに
}
}
//
// StatusBarConfigurator.swift
// StatusBarTest
//
// Created by hiroki on 2021/02/11.
//
import UIKit
import SwiftUI
class StatusBarConfigurator: ObservableObject {
private var window: UIWindow?
var statusBarStyle: UIStatusBarStyle = .default {
didSet {
window?.rootViewController?.setNeedsStatusBarAppearanceUpdate()
}
}
fileprivate func prepare(scene: UIWindowScene) {
if window == nil {
let window = UIWindow(windowScene: scene)
let viewController = ViewController()
viewController.configurator = self
window.rootViewController = viewController
window.frame = UIScreen.main.bounds
window.alpha = 0
self.window = window
}
window?.windowLevel = .statusBar
window?.makeKeyAndVisible()
}
fileprivate class ViewController: UIViewController {
weak var configurator: StatusBarConfigurator!
override var preferredStatusBarStyle: UIStatusBarStyle { configurator.statusBarStyle }
}
}
fileprivate struct SceneFinder: UIViewRepresentable {
var getScene: ((UIWindowScene) -> ())?
func makeUIView(context: Context) -> View { View() }
func updateUIView(_ uiView: View, context: Context) { uiView.getScene = getScene }
class View: UIView {
var getScene: ((UIWindowScene) -> ())?
override func didMoveToWindow() {
if let scene = window?.windowScene {
getScene?(scene)
}
}
}
}
extension View {
func prepareStatusBarConfigurator(_ configurator: StatusBarConfigurator) -> some View {
return self.background(SceneFinder { scene in
configurator.prepare(scene: scene)
})
}
}
@Nikaaner
Copy link

Nikaaner commented Jul 2, 2021

Good helper. But once I caught a crash:

0x000000010247fc48 Swift runtime failure: Unexpectedly found nil while implicitly unwrapping an Optional value + 0 (StatusBarConfigurator.swift:0)
0x000000010247fc48 preferredStatusBarStyle.get + 0 (StatusBarConfigurator.swift:37)

@Hiroki-Kawakami
Copy link
Author

Hiroki-Kawakami commented Jul 3, 2021

StatusBarConfigurator needs to be generated only once per Scene.
You should generate it in the view directly under WindowGroup, and share it using Environment.

@Nikaaner
Copy link

Nikaaner commented Jul 5, 2021

I did it, thank you.

@nickfl
Copy link

nickfl commented Feb 13, 2022

Again about light or dark, what about red, green, or blue, or some custom color?

@ashinthetray
Copy link

ashinthetray commented Mar 24, 2022

Hi there, thanks for this solution.

I noticed that after you set the statusBarStyle to light/dark and then set it back to .default and then iOS will not display the status bar in the correct color when modals (.sheet) are displayed.

Usually iOS inverts the status bar color automatically when modals are displayed, so it appears that behavior gets disabled with this implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment