Adding Dark Mode to iOS App

Adding dark mode to iOS app

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).

How to support support dark mode for existing swift app iOS?

if you don't set UIUserInterfaceStyle on info.plist then the App automatically enables dark mode depends on the system. If the system runs with dark mode then the app will start with dark mode.

But to show all the texts or other things you have to use the system color as the background and also for texts. OR you can use a custom color for dark mode or light mode.

Setting app to have light/dark mode without the use of the device setting

Dark mode has so many options and the code to manage this can be overwhelming and error prone that we decided to look at what our users needed/wanted and then we opted for the most natural (and less code intensive) approach to supporting dark mode.

We decided we wanted our app to follow the these principles:

  1. Default is that App switches when the Device switches (unless user overwrites in control center, the app switches when the local time zone turns dark/light.
  2. Some users have a preference for using the app in one mode always. We added an option to overwrite the default device setting to always present the app in that mode.
  3. Choice of colors can be overwhelming to the user. We already had options for the user to change color for notifications (alerts/Banners etc) so when it came to picking color sets for light/dark mode we opted to use system colors, which makes the implementation much easier and manages the switch between modes automatically.

The following is not a complete solution, but code snippets to help you get started with your solution:

  1. User setting overwriting Mode

     override func viewDidLoad() {
    super.viewDidLoad()
    // get the user option and set the relevant mode
    if Options.userDarkModeOption == .dark {
    overrideUserInterfaceStyle = .dark
    } else {
    overrideUserInterfaceStyle = .light
    }
    }
  2. Detecting Mode change (user in control center or local time change)

     override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    // Update user interface if changes to Dark Mode detected.
    MasterTable.reloadData()

    }

  3. Override default colors (try to limit to specific cases)

     switch traitCollection.userInterfaceStyle {
    case .dark :
    cell?.contentView.backgroundColor = = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1)
    case .light :
    cell?.contentView.backgroundColor = UIColor.systemTeal
    case .unspecified :
    cell?.contentView.backgroundColor = UIColor.systemOrange
    @unknown default:
    print("Error: Unknown userInterFaceStyle in masterVC/cellforitem")
    }

How to change app theme (Light/Dark) programmatically in swift 5

In your app delegate didFinishLaunchingWithOptions read the preference from shared defaults and do something like this:

UIApplication.shared.keyWindow.overrideUserInterfaceStyle = preference == "dark" ? .dark : .light

In your Settings view controller do the same when the user changes the preference.

Setting the override on your window should take care of all view controllers.

How to use dark mode in simulator iOS 13?

In Settings, scroll down to Developer and then Dark Appearance

Sample Image


Update

In addition to the above, there are now many other ways to enable dark appearance in the simulator, as shown in the many great answers below.

• Change Environment Overrides from Xcode (@AshCameron)

• Toggle Appearance A from the Simulator menu (@Shredder2794)

• Update from the command line using xcrun simctl ui booted appearance … (@blackjacx, @tadija)

• Programmatically using overrideUserInterfaceStyle = .dark (@thisIsTheFoxe)

• Specify UIUserInterfaceStyle in your info.plist (@DhavalGevariya)

• Use SimGenie from Curtis Herbert…  https://simgenie.app

Change between Light and Dark mode for entire app

You could set it on your app's window (it is also a view):

window.overrideUserInterfaceStyle = .dark

Edit:
Add to SceneDelegate file in the willConnectTo func

For example:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
window?.overrideUserInterfaceStyle = .light
}


Related Topics



Leave a reply



Submit