Skip to content

Instantly share code, notes, and snippets.

@bdashore3
Created April 10, 2022 15:38
Show Gist options
  • Save bdashore3/72b6e29111b6724c1c30c471325c4d44 to your computer and use it in GitHub Desktop.
Save bdashore3/72b6e29111b6724c1c30c471325c4d44 to your computer and use it in GitHub Desktop.
SwiftUI Status bar customization
//
// ContentView.swift
// TestingGround
//
// Created by Brian Dashore on 12/31/21.
//
import SwiftUI
import Introspect
struct ContentView: View {
@StateObject var hostingViewController: HostingViewController = .init(rootViewController: nil, style: .default)
@State var bgColor: Color = .yellow
@State var showSheet: Bool = false
var body: some View {
ZStack {
bgColor
.ignoresSafeArea()
VStack(spacing: 30) {
Button("Light color") {
bgColor = .yellow
}
Button("Dark color") {
bgColor = .black
}
Button("Show sheet") {
showSheet.toggle()
}
.sheet(isPresented: $showSheet) {
InnerView()
}
}
}
// You can use any way to grab the rootViewController, but I want to use Introspect
.introspectViewController { viewController in
// Grab the root view controller from the UIWindow and set that to the hosting controller
let window = viewController.view.window
guard let rootViewController = window?.rootViewController else { return }
hostingViewController.rootViewController = rootViewController
// Ignore system dark mode color inversion
hostingViewController.ignoreDarkMode = true
// Hide the statusbar. Overriding the hosting controller disables the statusbar view modifier
hostingViewController.isHidden = false
// Set the window's root view controller to the hosting controller subclass
window?.rootViewController = hostingViewController
}
.onChange(of: bgColor) { newColor in
// darkContent is used for light backgrounds and vice versa
if newColor.isLight {
hostingViewController.style = .darkContent
} else {
hostingViewController.style = .lightContent
}
}
}
}
struct InnerView: View {
var body: some View {
Text("Hello, world!")
}
}
//
// HostingViewController.swift
// TestingGround
//
// Created by Brian Dashore on 4/7/22.
//
import UIKit
// Inspired by Thomas Rademaker's UIHosting controller
// Article: https://barstool.engineering/set-the-ios-status-bar-style-in-swiftui-using-a-custom-view-modifier/
// This code is used in Asobi if you want a practical example:
// https://github.com/bdashore3/Asobi/blob/next/Asobi/RepresentableViews/HostingViewController.swift
// ObservableObject to observe statusbar changes and set accordingly
class HostingViewController: UIViewController, ObservableObject {
// The main controller to customize
var rootViewController: UIViewController?
// The statusbar style, updates on change
var style: UIStatusBarStyle = .lightContent {
didSet {
// Can remove the animation block
UIView.animate(withDuration: 0.3) {
self.rootViewController?.setNeedsStatusBarAppearanceUpdate()
}
}
}
// If the statusbar is hidden. Subclassing breaks SwiftUI's statusbar modifier, so handle hiding here
var isHidden: Bool = false {
didSet {
// Can remove the animation block
UIView.animate(withDuration: 0.3) {
self.rootViewController?.setNeedsStatusBarAppearanceUpdate()
}
}
}
// Ignore dark mode color inversion
var ignoreDarkMode: Bool = false
init(rootViewController: UIViewController?, style: UIStatusBarStyle, ignoreDarkMode: Bool = false) {
self.rootViewController = rootViewController
self.style = style
self.ignoreDarkMode = ignoreDarkMode
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
guard let child = rootViewController else { return }
addChild(child)
view.addSubview(child.view)
child.didMove(toParent: self)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
if ignoreDarkMode || traitCollection.userInterfaceStyle == .light {
return style
} else {
if style == .darkContent {
return .lightContent
} else {
return .darkContent
}
}
}
override var prefersStatusBarHidden: Bool {
return isHidden
}
// Can change this to whatever animation you want
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .fade
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
setNeedsStatusBarAppearanceUpdate()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment