Outdated PostCheck out my new post, "Name", to learn how to support native Dark Mode in iOS 13.
While dark mode is now available for both tvOS and macOS, it is still not available on iOS. In this tutorial, we’ll see how to set up a custom in-app theming system which lets the user pick between light and dark mode within the app, providing an alternative to a system dark mode.
We’ll also use observers to allow view controllers and other classes to register for appearance updates, so they can update their appearance whenever the theme is changed by the user.
If you’d like to see the final version of this tutorial, feel free to jump to the Conclusion, where you’ll find a Swift Playground containing all the code from this tutorial.
First, we simply need to create a list of the themes that our app supports, which we can then use to know the current and new theme.
We’ll create an enum with our theme options, light and dark:
Now that our app knows which themes we support, we’ll need to add a way to let classes such as view controllers observe the current theme, so when the user switches theme the class can update its UI or appearance accordingly.
Create a protocol named
AppearanceObserver, which classes can then conform to so that we can tell them the theme changed. The protocol will have a single function, which we’ll call whenever the theme changes.
Next, we need a way to store a list of objects that are observing theme changes, so we can call them when needed. It will also allow classes to register or deregister themselves as observers whenever they want.
We’ll create a
Observation struct, which will contain our list of observers as well as two simple functions to add or remove an observer. When adding or removing an observer, we’ll use their
ObjectIdentifier, which is a unique value given to each instance of a class, to make sure all of our observers are unique.
Additionally, we’ll add a convenience function to our
Observation class, which will handle looping through and notifying each observer. This way, we can call
Observation.themeDidChange(to: theme) to quickly notify all the observers.
Finally, we need to call the function we just created whenever the current theme changes, so we’ll add a
didSet observer on our
current variable in the
Theme enum we created earlier. To do this, we just need to replace the
current variable from earlier with this implementation, which tells
Observation to notify all observers whenever the theme changes.
Now that the theming system is in place, any view controller can conform to the
AppearanceObserver protocol and register themselves as an observer to receive updates whenever the theme changes.
However, there are still a few optional things we can do to improve the experience as a whole. Again, these improvements are optional, so if you’d like to skip them, feel free to jump directly to the Conclusion.
Easier Theme Switching
The first of the improvements will make it easier for whichever view controller changes the theme, such as a
SettingsViewController, to toggle between the themes without a lot of logic, using an extension to
CaseIterable. By using an extension, we can avoid putting this logic in our view controller, calling
Remember: This will not work correctly if you have more than 2 themes!
Please note the following code is taken from Paul Hudson’s tweet and all credit goes to him.
The other way to improve the theme switching is to animate theme switches, making the transition smoother and more visually pleasing.
This is easily done by using
animate method, as in this example:
Note that to change the duration of the animation, you can simply change the duration from 0.6 and make it higher or lower (in seconds).
In this tutorial, we’ve gone through a clean implementation of a theming system, which allows for easy theme changes and notifications.
While iOS still doesn’t have a built in dark mode, using a system such as this will make it even easier to eventually utilize a system dark mode, as we can just switch to the system’s setting on the new iOS version and keep this implementation for users on older versions.
I’ve made a Swift Playground which demonstrates the system implemented within this tutorial with a
DemoViewController, which shows how to observe theme changes and update the appearance of the app accordingly. You can see a recording of this at the top of this blog post, or download the playground yourself below.
I hope this tutorial helped you out! If you have any questions or feedback, feel free to send them or email [email protected]!
Thanks for reading 🙃