Implement dark mode switch in SwiftUI App
A demo of using @AppStorage
to switch dark mode
PS: For global switch, modifier should be added to WindowGroup/MainContentView
import SwiftUI
struct SystemColor: Hashable {
var text: String
var color: Color
}
let backgroundColors: [SystemColor] = [.init(text: "Red", color: .systemRed), .init(text: "Orange", color: .systemOrange), .init(text: "Yellow", color: .systemYellow), .init(text: "Green", color: .systemGreen), .init(text: "Teal", color: .systemTeal), .init(text: "Blue", color: .systemBlue), .init(text: "Indigo", color: .systemIndigo), .init(text: "Purple", color: .systemPurple), .init(text: "Pink", color: .systemPink), .init(text: "Gray", color: .systemGray), .init(text: "Gray2", color: .systemGray2), .init(text: "Gray3", color: .systemGray3), .init(text: "Gray4", color: .systemGray4), .init(text: "Gray5", color: .systemGray5), .init(text: "Gray6", color: .systemGray6)]
struct DarkModeColorView: View {
@AppStorage("isDarkMode") var isDarkMode: Bool = true
var body: some View {
Form {
Section(header: Text("Common Colors")) {
ForEach(backgroundColors, id: \.self) {
ColorRow(color: $0)
}
}
}
.toolbar {
ToolbarItem(placement: .principal) { // navigation bar
Picker("Color", selection: $isDarkMode) {
Text("Light").tag(false)
Text("Dark").tag(true)
}
.pickerStyle(SegmentedPickerStyle())
}
}
.modifier(DarkModeViewModifier())
}
}
private struct ColorRow: View {
let color: SystemColor
var body: some View {
HStack {
Text(color.text)
Spacer()
Rectangle()
.foregroundColor(color.color)
.frame(width: 30, height: 30)
}
}
}
public struct DarkModeViewModifier: ViewModifier {
@AppStorage("isDarkMode") var isDarkMode: Bool = true
public func body(content: Content) -> some View {
content
.environment(\.colorScheme, isDarkMode ? .dark : .light)
.preferredColorScheme(isDarkMode ? .dark : .light) // tint on status bar
}
}
struct DarkModeColorView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
DarkModeColorView()
}
}
}
Single View
To change the color scheme of a single view (Could be the main ContentView
of the app), you can use the following modifier:
.environment(\.colorScheme, .light) // or .dark
or
.preferredColorScheme(.dark)
Also, you can apply it to the ContentView
to make your entire app dark!
Assuming you didn't change the ContentView
name in scene delegate or @main
Entire App (Including the UIKit
parts and The SwiftUI
)
First you need to access the window to change the app colorScheme that called UserInterfaceStyle
in UIKit
.
I used this in SceneDelegate
:
private(set) static var shared: SceneDelegate?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
Self.shared = self
...
}
Then you need to bind an action to the toggle. So you need a model for it.
struct ToggleModel {
var isDark: Bool = true {
didSet {
SceneDelegate.shared?.window!.overrideUserInterfaceStyle = isDark ? .dark : .light
}
}
}
At last, you just need to toggle the switch:
struct ContentView: View {
@State var model = ToggleModel()
var body: some View {
Toggle(isOn: $model.isDark) {
Text("is Dark")
}
}
}
From the UIKit part of the app
Each UIView
has access to the window, So you can use it to set the . overrideUserInterfaceStyle
value to any scheme you need.
myView.window?.overrideUserInterfaceStyle = .dark