Backward compatible Dark Mode on iOS

Implementing backward compatible Dark Mode on iOS

Maksym Shcheglov
4 min readOct 6, 2019

Earlier this year Apple announced Dark Mode on iOS, that offers an option for a user to choose a system-wide light or dark appearance. It is available starting from iOS 13. But if you aren’t that lucky with the iOS adoption rate, there is still a need to support older versions of iOS. In this article let’s take a look at how we can introduce Dark Mode that works on all iOS versions including iOS 13.

Pre-requisites

It should be pointed out that adopting the Dark Mode(or Dark Theme) is not that trivial as it might look at a first glance. Thus there is some preparation required before you move forward. It makes sense bringing some structure to the UI code and defining the application-wide color palette, fonts, UI elements styles, etc.

I’d suggest watching the WWDC talk Implementing Dark Mode on iOS and checking the documentation to get an overview of this feature and related changes introduced in UIKit. There is also a nice guide on how to get your app ready for Dark Mode posted at NSHipster.

Dark Mode before iOS 13

The first thing we should do is to declare a theme type. It defines a style that can be applied to the entire app and specifies appearance details of every view. The Theme struct contains a nested Type enum and two properties that store theme type and color palette:

Let’s assume that we have a color palette defined in the project. We can create light and dark themes then:

To be able to update our UI components with the current theme there should be a protocol that all of those conform to. This can be achieved with a Themeable protocol:

Next, we’ll need a way to keep track of the current application theme and provide an option to subscribe to the current theme changes. For that we’ll declare a ThemeProvider class which stores the current theme in the UserDefaults, keeps theme observers in NSHashTableand notifies those when the current theme gets changed:

With the above-mentioned code in place, we’re now ready to update our application and make it work in both dark and light modes. We can start with updating all UI components like view controllers, cells, controls, etc. to conform to the Themeable protocol and update its appearance in apply(theme:) method. To listen to the theme changes we should register observers by calling register(observer:) method from ThemeProvider class. It can be done for the view controllers as following:

Apart from that, there should be a settings screen where the user can turn the dark mode on and off. It could be done even nicer using the Gagat library and having an interactive way to switch between two different themes in your iOS application using a two-finger pan. Finally, we should get Dark Mode support for the application running on iOS 12.

Adopting Dark Mode in iOS 13

In iOS 13 Apple provided system-wide Dark Mode support. Now we can create colors and images in the asset catalog and specify a different appearance for Dark Mode. Eventually, when a user turns on dark mode from the Settings, the application’s UI can be updated automatically. There might be the case when you want to detect appearance changes and update the UI accordingly. This could be done by implementing traitCollectionDidChange(_:) method in any UIKit class that conforms to UITraitEnvironment protocol. Taking all of this into account, we should make some adjustments to the current solution.

Leveraging dynamic colors we can define adaptive color palette and theme:

The ThemeProvider we’ve described before won’t work nicely for applications running on iOS 13. We can declare a new DefaultThemeProvider class and make both providers conform to the ThemeProvider protocol:

Next we’ll need a way to pick a ThemeProvider implementation based on the current iOS version:

With that implemented we should get Dark Mode support for the application running on iOS 13.

Conclusion

In this article, we’ve explored how to make your app look great by implementing Dark Mode that works on all iOS versions including iOS 13. The solution we’ve described here could be easily extended to support more than two themes for applications running on iOS 12 and below. You can find the demo project’s source code on Github. Feel free to play around and reach me out on Twitter if you have any questions, suggestions or feedback.

Thanks for reading!

--

--

Maksym Shcheglov

Software Engineer with more than a decade of experience · Author of http://OnSwiftWings.com · Follow me on twitter.com/sgl0v