Modernizing iOS Apps for Dark Mode with Xamarin

David Ortinau

With iOS 13, Apple introduces dark mode: A system-wide option for light and dark themes. iOS users may now choose the theme or allow iOS to dynamically change appearance based on the environment and time. Xamarin.iOS and Xamarin.Forms deliver native iOS experiences to keep your applications looking shiny and fresh no matter what time of day.

Turning on Dark Mode

Before testing that your work to support appearance modes actually works, you’ll need to put your simulator into dark mode. In your chosen iOS 13 simulator, open Settings and scroll down to Developer. There you will find a switch for turning dark appearance ON or OFF. It only takes a second to see changes take effect across the entire simulator environment. Now you are ready to customize your app for both modes, as well as see your UI respond instantly!

Turning on Dark Mode


Assets for Light and Dark Modes

The iOS Asset Catalog in Visual Studio now supports supplying optional images and colors for each appearance mode. When using this functionality, iOS will automatically choose the appropriate image and color for you. Open your Assets.xcassets file in your iOS project and add a New Image set. Notice you can specify universal, dark, and light images at any of the target resolutions. In this example, there is an image for dark and for light with the name of “MicrosoftLogo”.

Assets for Light and Dark Modes img1

Additionally, notice there are also colors specified for “BackgroundColor” and “TitleColor”. Those colors are now available by name to be used throughout the application. The “BackgroundColor” has been assigned to the background of the view, and the “TitleColor” to the label you see on screen.

Assets for Light and Dark Modes img2

Docs: Dark Mode for iOS 13 Preview

Xamarin.Forms

All of the above applies to Xamarin.Forms as well! We know many of you prefer to share as much code as possible, and that includes styles. The Xamarin.Forms team has drafted a proposal for how we might surface appearance mode support and semantic colors. Be sure to review that and add your comments.

Let’s take a look at how difficult it is to support Dark Mode in Xamarin.Forms today. (HINT: it’s really easy 😉 )

In this sample app, Xappy, there are resource dictionaries for dark and light themes. Each theme builds upon the base of shared styles for the application.

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Xappy.Styles.DarkTheme"
             Source="DefaultTheme.xaml">

    <Color x:Key="backgroundColor">#FF000000</Color>
    
    <Color x:Key="TextPrimaryColor">#B0FFFFFF</Color>
    <Color x:Key="TextSecondaryColor">#B0FFFFFF</Color>
    <Color x:Key="TextTernaryColor">#C8C8C8</Color>
</ResourceDictionary>
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Xappy.Styles.WhiteTheme"
             Source="DefaultTheme.xaml">

    <Color x:Key="backgroundColor">#FFFFFFFF</Color>

    <Color x:Key="TextPrimaryColor">#B0000000</Color>
    <Color x:Key="TextSecondaryColor">#80000000</Color>
    <Color x:Key="TextTernaryColor">#C8C8C8</Color>
</ResourceDictionary>

To make sure the colors update when the styles are changed, you’ll want to use the DynamicResource  markup extension. If no updates or changes are needed, use the StaticResource markup extension.

Shell.BackgroundColor="{DynamicResource backgroundColor}"
BackgroundColor="{DynamicResource backgroundColor}"
Shell.TitleColor="{StaticResource cerulean}"

Since we are using cross-platform styling here, we need to manage iOS changes in appearance mode. This is easily done in a page renderer by listening to the UITraitCollection change. This is the PageRenderer added to the Xappy.iOS project:

using System;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using Xappy.Styles;

[assembly: ExportRenderer(typeof(ContentPage), typeof(Xappy.iOS.Renderers.PageRenderer))]
namespace Xappy.iOS.Renderers
{
    public class PageRenderer : Xamarin.Forms.Platform.iOS.PageRenderer
    {
        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null || Element == null)
            {
                return;
            }

            try
            {
                SetAppTheme();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"\t\t\tERROR: {ex.Message}");
            }
        }

        public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
        {
            base.TraitCollectionDidChange(previousTraitCollection);
            Console.WriteLine($"TraitCollectionDidChange: {TraitCollection.UserInterfaceStyle} != {previousTraitCollection.UserInterfaceStyle}");

            if(this.TraitCollection.UserInterfaceStyle != previousTraitCollection.UserInterfaceStyle)
            {
                SetAppTheme();
            }

            
        }

        void SetAppTheme()
        {
            if (this.TraitCollection.UserInterfaceStyle == UIUserInterfaceStyle.Dark)
            {
                if (App.AppTheme == "dark")
                    return;
                
                App.Current.Resources = new DarkTheme();

                App.AppTheme = "dark";
            }
            else
            {
                if (App.AppTheme != "dark")
                    return;
                App.Current.Resources = new WhiteTheme();
                App.AppTheme = "light";
            }
        }
    }
}

This code applies the current theme if it has changed when the view is displayed. This makes sure the app starts in the right mode. Then any time iOS notifies the app that traits have changed, such as UIUserInterfaceStyle, the code checks that the change is different than the styles currently being displayed as tracked by AppTheme, a simple variable for tracking this.

In the future, Xamarin.Forms will support this more directly (read the proposal), but as you can see it’s very little code to update your existing Xamarin.Forms code to adopt these new features of iOS 13.

Xamarin.Forms Dark Mode

Xappy on GitHub: https://github.com/davidortinau/Xappy/tree/feature/dark13/

Get Started with iOS 13 Preview

Since early July we have been shipping updates as Apple releases new Xcode 11 betas. The Xcode beta can be installed side-by-side with a stable version of Xcode. You can always return to the stable release channel in order to use your original production environment.

Important note: this preview requires a Mac running macOS 10.14.4 (Mojave) or newer! macOS 10.15 (Catalina) is supported in this release. Although you may need to take additional steps to install the preview.

Installation

To get started:

  1. Download and install the Xcode 11 beta from the Apple Developer Portal.
  2. In Visual Studio for Mac, select Visual Studio > Check for Updates, select the Xcode 11 Previews channel, and install the available updates.
  3. In Visual Studio for Mac, select Visual Studio > Preferences > Projects > SDK Locations > Apple and select Xcode-beta.app.
  4. (Optional – Visual Studio 2019 only) Download and install the Xcode 11 preview support VSIX.

That’s it! Begin building your apps against Xcode 11 and utilize the available iOS 13 APIs within your Xamarin apps. Detailed instructions can be found on the Xamarin Documentation Portal.

Update (September 4th): Preview 6 of our support for Xcode 11 and iOS 13 is now available. Huge thanks for community contribution to AVKit. Read our release notes for more information, and visit the forums to discuss.

Share Your Experience

We want to hear how our Xcode 11 preview support works for you! Build your application against Xcode 11 using today’s preview to ensure that your app(s) continues to build using this preview. After, start integrating new APIs such as Sign in with Apple in your app(s). Our priorities are driven by your feedback. Let us know which features from Xcode 11 and iOS 13 are important to you. Complete our survey.

Please log an issue on GitHub with any feedback.

3 comments

Discussion is closed. Login to edit/delete existing comments.

  • Chris Windram 0

    Thank you for this article. It worked a treat for me, and my Xamarin app now has dark mode support.

    The only thing I needed to tweak was that previousTraitCollection might be null in pre iOS 13 versions of iOS, so I just needed to add some null checking logic to prevent the TraitCollectionDidChange function from bombing out.

    Thanks again for the helpful snippets, and saving me a load of headaches figuring it out!

  • Ruben Carreon Gama 0

    Hello

    Great article

    Do you have an example for android?

    Cannot make it work, even looking at the xappy app

  • John Livermore 0

    HI, I have VS2019 16.4.4. When I add an image set I am not seeing the tabs for light/dark modes. What am I missing here? Or does it only work with VS for Mac?

Feedback usabilla icon