How can I make a switch to change programatically to dark or light mode in my iOS app? I'm using Swift.
-
1https://stackoverflow.com/questions/57701467/turn-on-system-dark-mode-from-app-swift – Mohammad Eslami Feb 11 '20 at 14:39
-
1It is very much possible. See below. – Frank Rupprecht Feb 11 '20 at 14:49
-
1... assuming that you mean "for your app" and not the system setting. – Frank Rupprecht Feb 11 '20 at 14:49
-
Full Swift https://betterprogramming.pub/swiftui-app-theme-switch-241a79574b87 – kelalaka Apr 11 '23 at 14:34
3 Answers
You can override the style for single views or view controller using the overrideUserInterfaceStyle
property. But since the window is also a view, you can set that on your main window to force it into light or dark mode:
window.overrideUserInterfaceStyle = .dark

- 9,191
- 31
- 56
-
3FYI: if the view controllers is added into a navigation controller, then it's the later which must have this property set. `dummyNavigation.overrideUserInterfaceStyle = .light/.dark` – Mauricio Chirino Jan 03 '21 at 15:14
-
-
2To override the entire app, you should be able to use UIWindow().overrideUserInterfaceStyle = UIUserInterfaceStyle.light – Jorge Zapata Aug 07 '21 at 14:21
-
2@JorgeZapata just to clarify, that won't do anything. (It will apply the override to an unused instance of UIWindow.) Callers should get the window (if available) from the current Scene, which is typically but not always the first Scene in UIApplication's connectedScenes. Cheers. – Womble Aug 10 '21 at 03:14
I want to elaborate more on the answer provided by @Frank Schlegel.
To change theme from another view controller in your app (which is what you originally asked for, I think) you could add an observer for a UserDefaults
value that will trigger the change.
I would add an enum to better represent the theme state
enum Theme: String {
case light, dark, system
// Utility var to pass directly to window.overrideUserInterfaceStyle
var uiInterfaceStyle: UIUserInterfaceStyle {
switch self {
case .light:
return .light
case .dark:
return .dark
case .system:
return .unspecified
}
}
}
In your SceneDelegate
under your window
initialisation you have to add this method that is triggered every time UserDefaults
changes value.
UserDefaults.standard.addObserver(self, forKeyPath: "theme", options: [.new], context: nil)
Also, you want to remove that observer when the SceneDelegate
is deinitialised, add
deinit {
UserDefaults.standard.removeObserver(self, forKeyPath: "theme", context: nil)
}
This will place an observer for that theme
value in UserDefaults
.
To handle changes you need to add this method to your SceneDelegate
class.
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard
let change = change,
object != nil,
keyPath == Defaults.theme.rawValue,
let themeValue = change[.newKey] as? String,
let theme = Theme(rawValue: themeValue)?.uiInterfaceStyle
else { return }
UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveLinear, animations: { [weak self] in
self?.window?.overrideUserInterfaceStyle = theme
}, completion: .none)
}
This will be executed every time theme
value changes in UserDefaults
and will animate the transition from a theme to another.
Now, to change your theme from other view controllers in your app you just need to change value for UserDefaults
.
UserDefaults.standard.setValue(Theme.light.rawValue, forKey: "theme")

- 1,265
- 1
- 18
- 31
-
5
-
looks promising, but how to do that if there's just a AppDelegate file? – blendstylez Dec 27 '22 at 19:50
You can use one of the observation ways, for example, Defaults
lib, and then add
window.overrideUserInterfaceStyle = .dark
to
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {}

- 1,265
- 1
- 18
- 31

- 807
- 1
- 12
- 31
-
-
1can you elaborate a bit more on how this could be used in an app to change to light or dark on the fly? – GarySabo Oct 07 '21 at 15:13