Created
September 11, 2024 18:36
-
-
Save chockenberry/a2a23a12604e333b1c2a8b71e7a24155 to your computer and use it in GitHub Desktop.
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
// | |
// ColorSchemeApp.swift | |
// ColorScheme | |
// | |
// Created by Craig Hockenberry on 9/11/24. | |
// | |
import SwiftUI | |
@main | |
struct ColorSchemeApp: App { | |
@AppStorage("preferredColorScheme") private var preferredColorScheme: ColorScheme? | |
var body: some Scene { | |
WindowGroup { | |
ContentView() | |
.preferredColorScheme(preferredColorScheme) | |
} | |
.onChange(of: preferredColorScheme) { oldValue, newValue in | |
print("ColorSchemeApp: preferredColorScheme: \(oldValue) -> \(newValue)") | |
} | |
} | |
} | |
// NOTE: This allows @AppStorage to store an optional ColorScheme | |
extension ColorScheme: RawRepresentable { | |
public init?(rawValue: Int) { | |
guard let userInterfaceStyle = UIUserInterfaceStyle(rawValue: rawValue), | |
userInterfaceStyle != .unspecified, | |
let colorScheme = ColorScheme(userInterfaceStyle) | |
else { | |
//print("ColorScheme init: colorScheme = nil, rawValue = \(rawValue)") | |
return nil | |
} | |
//print("ColorScheme init: colorScheme = \(colorScheme), userInterfaceStyle = \(userInterfaceStyle)") | |
self = colorScheme | |
} | |
public var rawValue: Int { | |
let userInterfaceStyle = UIUserInterfaceStyle(self) | |
//print("ColorScheme rawValue: self = \(self), userInterfaceStyle = \(userInterfaceStyle)") | |
return userInterfaceStyle.rawValue | |
} | |
} | |
// NOTE: This provides a display name for an optional ColorScheme | |
extension ColorScheme? { | |
public var displayName: String { | |
if let self { | |
switch self { | |
case .dark: return "Dark" | |
case .light: return "Light" | |
@unknown default: | |
return "Unknown" | |
} | |
} | |
else { | |
return "System" | |
} | |
} | |
} | |
struct ContentView: View { | |
@State private var presentSettings = false | |
@State private var identityHack: Int = 0 | |
@AppStorage("preferredColorScheme") private var preferredColorScheme: ColorScheme? | |
var body: some View { | |
VStack(spacing: 40) { | |
Text(preferredColorScheme.displayName).font(.headline) | |
Button("Show Settings") { | |
presentSettings = true | |
} | |
} | |
.padding() | |
.sheet(isPresented: $presentSettings) { | |
SettingsView() | |
.id(identityHack) // an attempt to force an update by changing structural identity | |
.preferredColorScheme(preferredColorScheme) | |
.onChange(of: identityHack) { oldValue, newValue in | |
print("SettingView: identityHack: \(oldValue) -> \(newValue)") | |
} | |
} | |
.onChange(of: preferredColorScheme) { oldValue, newValue in | |
print("ContentView: preferredColorScheme: \(oldValue) -> \(newValue)") | |
identityHack += 1 | |
} | |
} | |
} | |
struct SettingsView: View { | |
@Environment(\.dismiss) private var dismiss | |
@State private var path = NavigationPath() | |
@AppStorage("preferredColorScheme") private var preferredColorScheme: ColorScheme? | |
var body: some View { | |
NavigationStack(path: $path) { | |
Form { | |
Section { | |
Picker("Theme", selection: $preferredColorScheme) { | |
Text("System").tag(nil as ColorScheme?) | |
Text("Dark").tag(ColorScheme.dark as ColorScheme?) | |
Text("Light").tag(ColorScheme.light as ColorScheme?) | |
} | |
} | |
} | |
.toolbar { | |
ToolbarItem(placement: .confirmationAction) { | |
Button("Done") { | |
dismiss() | |
} | |
} | |
} | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} |
A full working solution is below:
//
// ColorSchemeApp.swift
// ColorScheme
//
// Created by Craig Hockenberry on 9/11/24.
//
import SwiftUI
@main
struct ColorSchemeApp: App {
@AppStorage("preferredColorScheme") private var preferredColorScheme: ColorScheme?
var body: some Scene {
WindowGroup {
ContentView()
.preferredColorScheme(preferredColorScheme)
}
}
}
// NOTE: This allows @AppStorage to store an optional ColorScheme
extension ColorScheme: RawRepresentable {
public init?(rawValue: Int) {
guard let userInterfaceStyle = UIUserInterfaceStyle(rawValue: rawValue),
userInterfaceStyle != .unspecified,
let colorScheme = ColorScheme(userInterfaceStyle)
else {
return nil
}
self = colorScheme
}
public var rawValue: Int {
let userInterfaceStyle = UIUserInterfaceStyle(self)
return userInterfaceStyle.rawValue
}
}
// NOTE: This provides a display name for an optional ColorScheme
extension ColorScheme? {
public var displayName: String {
if let self {
switch self {
case .dark: return "Dark"
case .light: return "Light"
@unknown default:
return "Unknown"
}
}
else {
return "System"
}
}
}
struct ContentView: View {
@State private var presentSettings = false
@AppStorage("preferredColorScheme") private var preferredColorScheme: ColorScheme?
@Environment(\.colorScheme) private var colorScheme
var body: some View {
VStack(spacing: 40) {
Text(preferredColorScheme.displayName).font(.headline)
Button("Show Settings") {
presentSettings = true
}
}
.padding()
.sheet(isPresented: $presentSettings) {
// NOTE: Using a nil value for .preferredColorScheme() _will not_ update the view. The workaround
// is to get the current colorScheme from the environment and use it instead (via optional chaining).
SettingsView()
.preferredColorScheme(preferredColorScheme ?? colorScheme)
}
}
}
struct SettingsView: View {
@Environment(\.dismiss) private var dismiss
@State private var path = NavigationPath()
@AppStorage("preferredColorScheme") private var preferredColorScheme: ColorScheme?
var body: some View {
NavigationStack(path: $path) {
Form {
Section {
Picker("Theme", selection: $preferredColorScheme) {
Text("System").tag(nil as ColorScheme?)
Text("Dark").tag(ColorScheme.dark as ColorScheme?)
Text("Light").tag(ColorScheme.light as ColorScheme?)
}
}
}
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
dismiss()
}
}
}
}
}
}
#Preview {
ContentView()
}
For reference, this method is not fully working and I can't really tell why.
I recorded a short video to showcase the issue (iOS 18 simulator, but same behavior in my device). As you'll see, I can toggle the system color scheme in the beginning and the app changes. However, when I change the setting to 'Dark' and then back to 'System', the app stays dark and no longer updates when the system color scheme changes.
ColorSchemeNotWorking.mp4
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The workaround is to change line 87 to the following:
And add the colorScheme environment to
ContentView
:The optional chaining on
preferredColorScheme
causes the currentcolorScheme
to be set explicitly.Thanks to Dimitri Bouniol for the inspriation.