Implementing Dark Mode on iOS

Bao Nguyen
Nov 6, 2019 · 5 min read

Last year, Dark Mode was one of the most exciting features of macOS Mojave. After waiting for what felt like an eternity, Dark Mode was finally announced at WWDC this year as a system-wide appearance in iOS 13. During WWDC, a group of iOS engineers and designers in our team banded together to plot out what it would take to adopt dark mode. It’s been such a tough journey, months of work and collaboration, much of code refactoring, to bring dark mode to our app. As such, we wanted to take some time to share how we approached and some of the obstacles that we encountered along the way.

API Discussion

Apple’s done a great job shaping how dark mode works in iOS 13. UIKit provides flexible and convenient APIs as well as excellent documentation. Most of the heavy lifting in selecting appropriate colors or images when transition between light and dark are done by UIKit. But after hours of experiments in these new APIs, we found some limitations.

  • Only light and dark are supported. So if we stick with Apple’s APIs, it will be impossible to have other custom themes.

“Standing on the shoulders of giants” and trying to stick with Apple’s APIs are great ideas but after hours of discussion, we decided to go with the approach of writing our own APIs that can resolve all of limitations above.

Building our own APIs

When building new APIs, we followed these key principles:

  • Lightweight: Has minimum overhead that achieves similar performance to UIKit APIs. Transition between themes must be smooth and animatable.

The first step in building new APIs is defining some handy primitives and concepts.

  • Dynamic colors: Colors that change in response to appearance changes.

Next we collaborated with our design systems team to define a set of color names as well as its hex values. After that, we built a tool to generate Objective-C code from our color palettes. The generated code for named dynamic colors looks like.

At this point, we have a set of AZColor objects. You may notice that we’re passing a color name into every initializer above. Each dynamic color object has a different name which will be used to resolve the color information at runtime.

This is how we resolve a named dynamic color.

Now that we have AZColor in the app, we can use it where we need to use dynamic colors. Our developers can freely use this without worrying about runtime crashes regardless of iOS version compatibility.

Another challenge we embraced was to get the work in selecting appropriate colors done automatically when transition between themes without any changes in code logic.

To do that, we decided to create an alternative getter/setter for every color properties.

For example, in UIView’s category, we have these new properties. In which, az_tintColor is the replacement for tintColor while az_backgroundColor is the replacement for backgroundColor.

In the setter, the given dynamic color will be registered in the receiver as an applier. Every time when the theme is changed, the applier is responsible for resolving the color information at that moment. After that the receiver’s property will be updated to the appropriate color as well.

Here’s an implementation of one of these getters/setters.

Once we had how the applier worked, we continued to write some macros that allow us to easily synthesize getter/setter for new properties without code duplication.

This is what ours looks like:

Properties in lower-level classes, like CALayer and CGColor, can be easily synthesized

From here, we had this pattern defined, the final step is to add more as they were needed. The set that we ended up with is:

  • AZColor as shown above.

Here’re some examples of how we combine them together in the app.

How we use dynamic images, dynamic colors or even dynamic attributed titles
Lower-level classes now work well with dynamic colors
There are two approaches for UIImageView

The only one thing that we have to do when user change their theme preference is:

Asset duplication

Another obstacle we encountered was to prevent asset duplication when adopting dark mode.

If you have multiple themes in your app, asset duplication can be a bad influence on your app size as well as maintainability. In order to resolve this we collaborated with our design team and decided to just use images that look good in both light and dark modes.

Some tricky things that we used for images are:

  • Gradient color can help maintain good contrast of image on both light and dark backgrounds.


We know that it’s been such a risk-return tradeoff between sticking with Apple’s APIs and writing our own one. However, by building our own APIs, we can easily add more amazing themes in the future, thereby satisfying our users by allowing them to choose their favorite appearance rather than forcing them to have just light or dark.

This week’s update to Zalo includes full support for dark mode in all iOS versions. And this approach has played an important role in the development process. Of course the actual implementation still wasn’t easy, our engineering and design teams had a deep dive into every part of the app. Admittedly, we probably missed some. Hope you all enjoy this update!

I hope this short tutorial will be useful to you. Let’s make your app look right at home on iOS 13. Cheers!

If you want to learn more about how these APIs work, feel free to leave your feedback below or you can always reach me on LinkedIn. See you in next post!

Flawless iOS

🍏 Community around iOS development, mobile design, and…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store