I am trying to add a theme to my app (a dark theme). So when the user clicks an activity switch it would then make the whole app go into the dark mode. I have hard coded the dark mode just to see what it would look like; however now I would like to be able to enable and disable it through and UISwitch, but I am not sure how to do this?
class DarkModeTableViewCell: UITableViewCell {
var DarkisOn = Bool()
let userDefaults = UserDefaults.standard
@IBOutlet var darkModeSwitchOutlet: UISwitch!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
@IBAction func darkModeSwitched(_ sender: Any) {
if darkModeSwitchOutlet.isOn == true {
//enable dark mode
DarkisOn = true
userDefaults.set(true, forKey: "DarkDefault")
userDefaults.set(false, forKey: "LightDefault")
} else {
//enable light mode
DarkisOn = false
userDefaults.set(false, forKey: "DarkDefault")
userDefaults.set(true, forKey: "LightDefault")
}
}
}
class DarkModeViewController: UIViewController {
func set(for viewController: UIViewController) {
viewController.view.backgroundColor = UIColor(red: 0.1, green: 0.1, blue: 0.1, alpha: 1.0)
viewController.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
viewController.navigationController?.navigationBar.tintColor = UIColor.white
viewController.navigationController?.navigationBar.barStyle = UIBarStyle.black
viewController.tabBarController?.tabBar.barStyle = UIBarStyle.black
}
static let instance = DarkModeViewController()
}
and then what I do is call the function in each one of the view controllers to see what it looks like, but I need to be able to access the bool value on if the switch is on or off and if it is then have it do that function, otherwise to just keep things the same. If you have any further questions, please let me know, I know some of this might not make to much sense.
In SwiftUI this was made far easier. Simply include the environment variable colorScheme
In the view and check it for dark mode like so:
struct DarkModeView: View {
@Environment(.colorScheme) var colorScheme: ColorScheme
var body: some View {
Text("Hi")
.foregroundColor(colorScheme == .dark ? .white : .black)
}
}
There’s a great article on how this all works here
To support dark mode in iOS 13 and above you can use the unicolor Closure.
@objc open class DynamicColor : NSObject{
public var light : UIColor
public var dark : UIColor
public init(light : UIColor,dark : UIColor) {
self.light = light
self.dark = dark
}
}
extension DynamicColor{
public func resolve() -> UIColor{
return UIColor.DynamicResolved(color: self)
}
}
extension UIColor{
class func DynamicResolved(color: DynamicColor) -> UIColor{
if #available(iOS 13.0, *) {
let dynamicColor = UIColor { (traitCollection: UITraitCollection) -> UIColor in
if traitCollection.userInterfaceStyle == .dark {
return color.dark
} else {
return color.light
}
}
return dynamicColor
} else {
// Fallback on earlier versions
return color.light
}
}
}
While using it in view
UIView().backgroundColor = DynamicColor(light: .white, dark: .black).resolve()
//OR
UIView().backgroundColor = UIColor.DynamicColor(light: .white, dark: .black)
From iOS 13 apple launched dark theme, If you want to add dark theme in your iOS app you can apply following lines of code on viewDidLoad() like:
if #available(iOS 13.0, *) {
overrideUserInterfaceStyle = .dark
} else {
// Fallback on earlier versions
}
So you can change theme like there is 2 options light or dark theme. But If you are writing above mentioned code it will take always dark theme only on iOS 13 running devices.
overrideUserInterfaceStyle = .light
Or if your device already running on iOS 13, you can change theme like:
you can even check the current set theme like:
if self.traitCollection.userInterfaceStyle == .dark{
print("Dark theme")
}else{
print("Light theme")
}
Let’s see the example:
override func viewDidLoad() {
super.viewDidLoad()
if self.traitCollection.userInterfaceStyle == .dark{
self.view.backgroundColor = UIColor.black
}else{
self.view.backgroundColor = UIColor.white
}
}
Results:
Here is the video for the same: https://youtu.be/_k6YHMFCpas
There are basically two ways to theme your App. Way number one: use Apple’s UIAppearance proxy. This works very well if your app is very consistent about color usuage across all your views and controls, and not so well if you have a bunch of exceptions. In that case I recommend using a third party pod like SwiftTheme
UPDATE: This question (and therefore, this answer) was written before iOS 13 was announced, therefore it does not use iOS 13 specific APIs.
I’d solve this using Notifications (NSNotificationCenter
APIs).
The idea is to notify your view controllers in real-time when the dark mode is enabled and when it is disabled, so they can also adapt to the change in real time. You don’t need to check the status of the switch or anything like that.
Start by creating two notifications (you can also do it with one only and pass in the desired theme in the userInfo
dictionary, but in this case it’s easier to create two notifications, since you need to cast and what-not with Swift).
NotificationsName+Extensions.swift
:
import Foundation
extension Notification.Name {
static let darkModeEnabled = Notification.Name("com.yourApp.notifications.darkModeEnabled")
static let darkModeDisabled = Notification.Name("com.yourApp.notifications.darkModeDisabled")
}
On all your “themable” view controllers, listen to these notifications:
override func viewDidLoad() {
super.viewDidLoad()
// Add Observers
NotificationCenter.default.addObserver(self, selector: #selector(darkModeEnabled(_:)), name: .darkModeEnabled, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(darkModeDisabled(_:)), name: .darkModeDisabled, object: nil)
}
Don’t forget to remove them in deinit
, since sending notifications to invalid objects raises an exception:
deinit {
NotificationCenter.default.removeObserver(self, name: .darkModeEnabled, object: nil)
NotificationCenter.default.removeObserver(self, name: .darkModeDisabled, object: nil)
}
In your “themable” view controllers, implement darkModeEnabled(_:)
and darkModeDisabled(_:)
:
@objc private func darkModeEnabled(_ notification: Notification) {
// Write your dark mode code here
}
@objc private func darkModeDisabled(_ notification: Notification) {
// Write your non-dark mode code here
}
Finally, toggling your switch will trigger either notification:
@IBAction func darkModeSwitched(_ sender: Any) {
if darkModeSwitchOutlet.isOn == true {
userDefaults.set(true, forKey: "darkModeEnabled")
// Post the notification to let all current view controllers that the app has changed to dark mode, and they should theme themselves to reflect this change.
NotificationCenter.default.post(name: .darkModeEnabled, object: nil)
} else {
userDefaults.set(false, forKey: "darkModeEnabled")
// Post the notification to let all current view controllers that the app has changed to non-dark mode, and they should theme themselves to reflect this change.
NotificationCenter.default.post(name: .darkModeDisabled, object: nil)
}
}
With this, all your view controllers will be notified in real time when the “theme” changes and they will react accordingly. Do note that you need to take measures to show the right mode when the app launches, but I’m sure you are doing that since you are using UserDefaults and presumably checking them. Also worth mentioning NSNotificationCenter is not thread-safe, although it shouldn’t matter since this all UI code that should go in the main thread anyway.
For more information, you can check the NSNotificationCenter documentation.
Note: This code is built upon what OP had. It can be simplified (you don’t need to keep track of both “light” and “dark” states for example, just one).