Created
April 10, 2022 15:38
-
-
Save bdashore3/72b6e29111b6724c1c30c471325c4d44 to your computer and use it in GitHub Desktop.
SwiftUI Status bar customization
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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!") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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