-
Notifications
You must be signed in to change notification settings - Fork 1.8k
[Spec] (iOS 13) Dark Mode & Semantic Colors #7304
Description
(iOS 13) Dark Mode & Semantic Colors
In preparation to the long awaited Dark Mode that is added by Apple to iOS 13, there are a couple of new APIs we can leverage and should surface to Xamarin.Forms.
Another thing we should consider is that there is a high contrast mode for accessibility purposes. While this mode has been available to iOS for a long time, there is not as much attention for it as Dark Mode. With Dark Mode introduced, that brings us to having four options:
- Default (being "Light Mode")
- Default High Contrast
- Dark Mode
- Dark Mode High Contrast
That's why it is also important to leverage the default functionality of iOS as much as possible. By using the color APIs provided by Apple as much as possible, we will get support for all these different modes for free.
Requirements
1. Built and released with Xcode 11 (or higher)
If you are using previous versions of Xcode, the app will always have its normal appearance, even if the user has Dark Mode enable on their device
2. Run on an iOS 13 device
Only devices with iOS 13 and up support Dark Mode. You cannot enforce Dark Mode on pre-iOS 13 versions. At least, not without your own custom code to achieve that.
If these two requirements are fulfilled, your app will automatically (try) to switch to Dark Mode whenever the user has enabled that on OS level.
However, if your app is not yet properly prepared because you might have used hard-coded colors, things might start to look bad. Since we are working hard on making clear that Xamarin.Forms UIs can be beautiful, let's make sure that doesn't happen to us.
API
Dynamic Colors
In iOS 13 dynamic colors are added. These colors will be different, depending on what mode is enabled for your app. For instance, if we look at SystemBlue this would be 0, 122, 255 (RGB) in default/light mode and in dark mode the RGB values shift to 10, 132, 255. A subtle difference. Again, if we think about high-contrast mode, this is automatically taken into account as well when using the System prefixed colors. The high-contract scheme also has two separate values for default and Dark Mode.

Top: system colors in "Light Mode" bottom: system colors in Dark Mode
Color
System Colors
| API | Description |
|---|---|
| Color.SystemBlue | Dynamic color for blue |
| Color.SystemGray | Dynamic color for gray |
| Color.SystemGreen | Dynamic color for green |
| Color.SystemIndigo | Dynamic color for indigo |
| Color.SystemOrange | Gets or sets Dynamic color for orange |
| Color.SystemPink | Dynamic color for pink |
| Color.SystemPurple | Dynamic color for purple |
| Color.SystemRed | Dynamic color for red |
| Color.SystemTeal | Dynamic color for teal |
| Color.SystemYellow | Dynamic color for yellow |
Note: I have just taken the Apple naming convention here. We might also consider an alternative. Maybe: SemanticBlue, SemanticGray, etc.
Since there will also be the possibility to define custom colors, although discouraged, we also need an API to access those.
| API | Description |
|---|---|
| Color.GetByName(string name) | Retrieves the developer-defined semantic color by name |
6 Shades of Gray
In addition, the iOS 13 APIs provide you with a range of six opaque gray colors which you can use in rare cases where translucency doesn't work well. The names for these are very creative.
| API | Description |
|---|---|
| Color.SystemGray | Dynamic color for gray (same as above) |
| Color.SystemGray2 | Dynamic color for gray2 |
| Color.SystemGray3 | Dynamic color for gray3 |
| Color.SystemGray4 | Dynamic color for gray4 |
| Color.SystemGray5 | Dynamic color for gray5 |
| Color.SystemGray6 | Dynamic color for gray6 |
Control Colors
Lastly, there are some colors that now specifically target the use of a certain control.
| API | Description |
|---|---|
| Color.Label | Text label that contains primary content |
| Color.SecondaryLabel | Text label that contains secondary content |
| Color.TertiaryLabel | Text label that contains tertiary content |
| Color.QuaternaryLabel | Text label that contains quaternary content |
| Color.PlaceholderText | Placeholder text in controls or text views |
| Color.Separator | Separator that allows some underlying content to be visible |
| Color.OpaqueSeparator | Separator that doesn't allow any underlying content to be visible |
| Color.Link | Text that functions as a link |
Detect Dark Mode
DeviceInfo
In the Device.DeviceInfo we add a property to be able to see if Dark Mode is enabled
| API | Description |
|---|---|
| IsDarkModeEnabled | Is Dark Mode enabled on this device |
Page
DarkModeChanged Event
To be able to respond to changes we expose this event on all pages. While this is detectable per UIView (XF: VisualElement) I deemed it only necessary to detect it on Page level.
| API | Description |
|---|---|
| DarkModeChanged(DarkModeEventArgs args) | Event that is fired whenever Dark Mode is enabled/disabled |
public class DarkModeEventArgs : EventArgs
{
public bool IsDarkModeEnabled { get; }
}
Force Appearance Mode
There is also the possibility to override the configured appearance mode. While this is overrideable per UIView (XF: VisualElement) I deemed it only necessary to detect it on Page level. Subviews will inherit automatically.
| API | Description |
|---|---|
| ForcedAppearanceMode | Property that lets the developer set a fixed appearance mode |
To support this, we would need to have an enum to be able to select the appearance mode we want to force
public enum AppearanceMode
{
DarkMode,
LightMode
}
VisualElementRenderer
This part is per definition iOS specific. In addition to all of the above, Apple also added new values to the UIBlurEffect.Styles. In addition to the values that are available since iOS 8 (None, ExtraLight, Light & Dark), values are added (systemUltraThinMaterial, systemThinMaterial, systemMaterial & systemThickMaterial) that will also adapt automatically to the appearance mode. In Xamarin.Forms we have a platform specific to set these, so we need to update this to be in line with the new APIs.
public enum BlurEffectStyle
{
None,
/// <summary>
/// Available in iOS 8.0 and later.
/// </summary>
ExtraLight,
/// <summary>
/// Available in iOS 8.0 and later.
/// </summary>
Light,
/// <summary>
/// Available in iOS 8.0 and later.
/// </summary>
Dark,
/// <summary>
/// Available in iOS 13.0 and later.
/// </summary>
SystemUltraThinMaterial,
/// <summary>
/// Available in iOS 13.0 and later.
/// </summary>
SystemThinMaterial,
/// <summary>
/// Available in iOS 13.0 and later.
/// </summary>
SystemMaterial,
/// <summary>
/// Available in iOS 13.0 and later.
/// </summary>
SystemThickMaterial,
}
Scenarios
You're laying in bed, the sun dropped behind the horizon. Crickets are softly chirping outside. You hear them, but they don't really catch your attention. You are already halfway asleep when your phone vibrates. It's a notification. The notification is spawned by your favorite app, built with your favorite framework: Xamarin.Forms.
While you subconsciously appreciate the beauty of Dark Mode on your brand new iOS 13 installation, you tap the notification and the app opens. Suddenly you can't see. You are blinded by a light as bright as the sun. Your phone drops onto your face to then drop even further to the ground. You grasp for air while quickly considering the options: aliens have finally invaded? Did your smart lights get hacked? Was your mom right and did your sight finally give out because of watching a screen too much?
Then it starts to sink in... This is the one app that does not support Dark Mode yet. Let's make sure this scenario will not become reality. Using Apple's words: “You really don’t want to be that one light appearance that’s stuck in dark appearance”.
Backward Compatibility
Since this adds only new APIs backwards compatibility should not be an issue.
If a user wants to opt-out and/or force a certain appearance, they could set the new UIUserInterfaceStyle key in the info.plist file. The value should then be light or dark. This will make the app always use one or the other and disregards the end-users setting. Of course, this is not recommended.
Difficulty : Low - Medium
It's mostly just opening up new API calls and make them accessible through the Xamarin.Forms layer. There might be some challenge in implementing the "detect which mode is on" functionality, since iOS does not have a clear-cut API for that.
Additional extra credit
App Icon
To support Dark Mode to its full extend, we might want to consider opening up the API that Apple has in place to be able to replace the App Icon on the Springboard. That way users can easily implement logic to offer different app icons to the end-users and have them choose between a light and dark themed icon
Mac OS support
While this spec is focussed towards iOS, let's not forget that Mac OS has a Dark Mode these days as well. It should be relatively easy to add support for Mac OS as well.
Android Support
Since Android is planning a similar dark mode (called dark theme) it might be good to plan to take that already into account. Since the infrastructure implemented for this already needs to reach out to the specific platform, it should not be too hard to map the Android dark theme specifics onto this new APIs.
Linked issue(s): #6483
