[Dev] How to add Dark Mode: The best way
Why is Dark Mode important:
To my surprise, Dark Mode has been the most requested feature for Pile. As a Dark Mode user myself, I had my doubts whether to prioritise a feature that doesn’t add any more value or functionality to the app, but the amount of requests made my decision clearer. Dark Mode had to be there for Pile!
Another reason Dark Mode is nowadays necessary for all apps is that iOS and almost all the other apps users use daily support it. Apps like Whatsapp, Instagram, Twitter, most newspapers, Line, and a long list. This means that when a user is used to see everything in Dark Mode and opens your app, if it’s not supported, there’s a big friction.
1 - Set up your styles in a StyleSheet file
Let’s start by the basics. Styles should be set by code. Always. It doesn’t matter if you are using Storyboards, Xibs, SwiftUI, or doing everything by code. You want to have references (@IBOutlets) of each view in your code and set styles there. If you use SwiftUI, it’s a lot easier, but the same principle stands.
The easiest way is to set up a file called StyleSheet and add all your styles there. This means Fonts, Colors, and even paragraph styles if needed. I usually have a struct similar to this:
There’s a few things I like about this approach. The code looks nicer, as your references clearly shows what you are setting (for example label.textColor = StyleSheet.Colors.pileRed, or label.font = StyleSheet.Fonts.p16).
This approach is also flexible for any specific needs your app might have. For example, Pile uses SF Symbols, so I have the default configuration for the UITabBar in StyleSheet.TabBar.symbolConfiguration, or I can set the radius of my cards, which appear in multiple places and in different ways in the StyleSheet.Constants.cardContentRadius.
And finally you can use Color Literals, which appear nicely in the Xcode editor 👆
2 - (How to) Choose a colour palette for Dark Mode:
Once you have your StyleSheet set up and all the code referencing it, you can choose a colour palette for Dark Mode. How? It’s actually easier than it sounds. Choose your favourite app that has a good-looking Dark Mode, and check their colours (you can use Digital Color Meter app, installed by default in your Mac). They’ll have a background colour, a font colour, navigation bar colours, a few ‘grays’ and the brand colours.
You can now copy them, and tweak them the way you want. Make sure to keep your ‘brand’ colors the way they are for Dark Mode. This way you can end up with a professional designer level colour palette, and still be identifiable by your users.
3 - How to setup Dark Mode:
I have a file called PileDefaults where I keep all my UserDefaults. The file looks like this:
In our Defaults file we also want to set the default value of useSystemDarkMode to true:
In my StyleSheet, I’ll have a darkMode: Bool variable that will tell me if we’re using DarkMode or not all around the app:
Now, we can start editing our StyleSheet with the colour palette we chose for DarkMode! By simply adding darkMode ? [dark mode colour] : [light mode (old) colour] in our Colors enum, we’ll have it.
Now, if we run the app in our device with iOS’s Dark Mode enabled, we should see all our app with Dark Mode!
4 - Fixing Launchscreen:
One thing you’ll notice is that the Launchscreen of your app is not in Dark Mode. This is annoying. In Pile’s case, the Launchscreen was super white, so users would see a white screen before going back to dark mode. We don’t want to burn anyone’s eyes!
The sad thing is that Launchscreen doesn’t support code references. So we’ll have to set it up in the Xib / Storyboard file. If you have images in your Launchscreen, you can configure them for dark mode too in .xcassets file by setting Appearances to Any, Dark:
5 - Listening to the System’s Dark Mode toggle:
At this point when the app launches it should correctly show either Dark or Light mode correctly. But what if the user changes it using the system’s when our app is launched or on the background?
We will override UIViewController’s function “traitCollectionDidChange” in our rootViewController to send our custom notification to any other view we want to update. We could override the same function everywhere instead of using the custom notification, but it’s better if we do it with our custom notification for the next step 6.
Now we just listen to the notification any important view we want to update in case dark mode changes:
Look that we’re reloading the collectionView (or could be a UITableView). Each cell usually has a method where you pass any info it needs to show. Make sure in that same method, which would be called in collectionView…cellForItemAtIndexPath…, also updates the colour styles:
6 - Going the extra mile: Letting your users choose:
For me, the user should be able to choose whether Dark Mode runs on your app or not. Regardless of what setting they use in their phone, they might prefer the app to run on the opposite setting - and that’s fine!
We just need to set up somewhere a toggle like this one:
The tricky side of this 👆 is that we’ll want to make sure the app reacts to this toggle accordingly by refreshing the styles when the user sets Dark Mode on or off.
But because we already use a custom notification for the System’s toggle (see step 5), it will be as easy as posting the same notification event when the toggle changes 🎉.
That’s it!
I hope you now have a much better idea on how to set up Dark Mode for your app, and you now know that it’s not that hard. And your users will be very happy!