I am currently trying to implement a solution in an app where the user is supposed to be able to switch the app's appearance in real-time with the following options:
Setting light and dark color schemes has proven to be quite easy and responsive using .preferredColorScheme(); however, I have not yet found any satisfying solution for the "System" option.
My current approach is the following:
My idea was to embed MainView in ContentView so that the @Environment(.colorScheme) would not be disturbed by any colorScheme that is applied to MainView.
However, it still doesn't work as supposed: When setting light and dark appearance, everything works as intended. However, when switching from light/dark to "system", the change in appearance is only visible after re-launching the app. Expected behavior, however, would be that the appearance changes instantly.
Any ideas on this?
Here are the relevant code snippets:
Main view
import SwiftUI
struct MainView: View {
@AppStorage("selectedAppearance") var selectedAppearance = 0
var body: some View {
VStack {
Spacer()
Button(action: {
selectedAppearance = 1
}) {
Text("Light")
}
Spacer()
Button(action: {
selectedAppearance = 2
}) {
Text("Dark")
}
Spacer()
Button(action: {
selectedAppearance = 0
}) {
Text("System")
}
Spacer()
}
}
}
ContentView
import SwiftUI
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
MainView()
.modifier(ColorSchemeModifier(colorScheme: colorScheme))
}
}
"Utilities"
import Foundation
import SwiftUI
struct ColorSchemeModifier: ViewModifier {
@AppStorage("selectedAppearance") var selectedAppearance: Int = 0
var colorScheme: ColorScheme
func body(content: Content) -> some View {
if selectedAppearance == 2 {
return content.preferredColorScheme(.dark)
} else if selectedAppearance == 1 {
return content.preferredColorScheme(.light)
} else {
return content.preferredColorScheme(colorScheme)
}
}
}
I ended up using the following solution which is a slight adaptation of the answer that @pgb gave:
ContentView:
struct ContentView: View {
@AppStorage("selectedAppearance") var selectedAppearance = 0
var utilities = Utilities()
var body: some View {
VStack {
Spacer()
Button(action: {
selectedAppearance = 1
}) {
Text("Light")
}
Spacer()
Button(action: {
selectedAppearance = 2
}) {
Text("Dark")
}
Spacer()
Button(action: {
selectedAppearance = 0
}) {
Text("System")
}
Spacer()
}
.onChange(of: selectedAppearance, perform: { value in
utilities.overrideDisplayMode()
})
}
}
Helper class
class Utilities {
@AppStorage("selectedAppearance") var selectedAppearance = 0
var userInterfaceStyle: ColorScheme? = .dark
func overrideDisplayMode() {
var userInterfaceStyle: UIUserInterfaceStyle
if selectedAppearance == 2 {
userInterfaceStyle = .dark
} else if selectedAppearance == 1 {
userInterfaceStyle = .light
} else {
userInterfaceStyle = .unspecified
}
UIApplication.shared.windows.first?.overrideUserInterfaceStyle = userInterfaceStyle
}
}