Implement dark mode switch in SwiftUI App
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
SwiftUI How to implement dark mode toggle and refresh all views
Just add .colorScheme
to your top View and add a color scheme variable (@State
, @Binding
, etc.). Whenever the variable changes the view (and children) update automatically.
struct ContentView: View {
@State var theColorScheme: ColorScheme = .dark
func toggleColorScheme() {
theColorScheme = (theColorScheme == .dark) ? .light : .dark
}
var body: some View {
ZStack { // or any other View
Color.primary // to make the change visible
Button(action: self.toggleColorScheme) {
Text("Toggle")
}
} .colorScheme(theColorScheme)
}
}
Note: if you want to update the color scheme of the whole presentation, you're better of using .preferredColorScheme
instead of .colorScheme
.
Manually set light/dark mode in SwiftUI and save users choice
Here is possible approach (scratchy, you can find here on SO property wrapper for defaults and use it for better styling, but the idea to achieve your goal is the same)
Tested with Xcode 11.4 / iOS 13.4
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private(set) static var shared: SceneDelegate?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
Self.shared = self
let contentView = ContentView()
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
// restore from defaults initial or previously stored style
let style = UserDefaults.standard.integer(forKey: "LastStyle")
window.overrideUserInterfaceStyle = (style == 0 ? .dark : UIUserInterfaceStyle(rawValue: style)!)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
...
}
struct ContentView: View {
var body: some View {
// manipulate with style directly in defaults
Toggle(isOn: Binding<Bool>(
get: { UserDefaults.standard.integer(forKey: "LastStyle") !=
UIUserInterfaceStyle.light.rawValue },
set: {
SceneDelegate.shared?.window!.overrideUserInterfaceStyle = $0 ? .dark : .light
UserDefaults.standard.setValue($0 ? UIUserInterfaceStyle.dark.rawValue : UIUserInterfaceStyle.light.rawValue, forKey: "LastStyle")
}
)) {
Text("is Dark")
}
}
}
SwiftUI: Force View to use light or dark mode
.colorScheme()
is deprecated does not work with the background well.
.preferredColorScheme()
is the way now. Does the job with the background too.
TestView1()
.preferredColorScheme(.dark)
TestView2()
.preferredColorScheme(.light)
Developer docs for .preferredColorScheme()
SwiftUI - Making a button suitable for light and dark mode
In you case you could simply do:
Text("Button \(button)")
.padding(.vertical, 12.5)
.padding(.horizontal, 120)
.foregroundStyle(.background)
.background(2 == button ? Color.primary: Color.secondary)
.clipShape(Capsule())
or for more control use:
@Environment(\.colorScheme) var colorScheme
var textColor: Color {
if colorScheme == .dark {
return Color.white
} else {
return Color.black
}
}
or follow @loremipsum and define your own colors in Assets
Is there a way to change dark to light theme and vice versa just like using a toggle programmitically in swift UI?
WindowGroup {
switch currentThemeRawValue {
case Theme.dark.rawValue:
ContentView().environment(\.colorScheme, .dark)
case Theme.light.rawValue:
ContentView().environment(\.colorScheme, .light)
default:
ContentView().environment(\.colorScheme, .dark)
}
}
The switch statement means that SwiftUI is populating the view with conditional content, so it would rebuild the entire hierarchy if the value changes. You only really want to change the environment value itself, so something like this would probably be better:
WindowGroup {
ContentView()
.environment(\.colorScheme, currentThemeRawValue == "dark" ?? .dark : .light)
}
SwiftUI SignInWithAppleButton dark mode
For some reason the button doesn't update automatically with the color scheme, but you can fix it with the code below:
struct AuthenticationView: View {
@Environment(\.colorScheme) private var colorScheme
let authManager = AuthManager()
private var buttonStyle: SignInWithAppleButton.Style {
switch colorScheme {
case .light: return .black
case .dark: return .white
@unknown default: return .black
}
}
var body: some View {
SignInWithAppleButton(
.signIn,
onRequest: authManager.createRequest,
onCompletion: authManager.handleResult
)
.signInWithAppleButtonStyle(buttonStyle)
.id(colorScheme)
.frame(height: 56)
}
}
SwiftUI dark / light mode change with @AppStorage
Why would you make the app responsible for the dark and light theme, I think you should let the system handle this. Other then that check your function. If you call ContentView it makes sense that it is shown.
And if you don't want the system to handle this. check this post How to switch programmatically to dark mode swift maybe this helps you
SwiftUI: How to let the user set the app appearance in real-time w/ options light, dark, and system?
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
}
}
Related Topics
How to Bring a View in Front of Another View, in Swift
Swift Override Instance Variables
How to Set Primary Key in Swift for Realm Model
Swift:How to Change Language Inside App
How to Show a Collectionview Like Facebook Upload Image Using Swift 4
In Swift, How to Let Other Apps Continue to Play Audio When My App Is Open
Exc_Bad_Access Error When Trying to Change Bool Property
How to Render a Whole Uitableview as an Uiimage in iOS
How to Create a User with Multiple Attributes in Firebase with Swift
Make Segue Programmatically in Swift
Swift Get String Between 2 Strings in a String
Upload Image to Server - Swift 3
Path to Bundle of iOS Framework
Xcode 5.1: Missing Required Architecture Arm64
Get Pixel Data as Array from Uiimage/Cgimage in Swift
iOS Game Center: Scores Not Showing on Leaderboard in Sandbox