directly from mexico, eduardo shares some knowledge on the xamarin platform. also, he is super into artificial intelligence, so you may also see some posts about that here.

iOS 13's Dark Mode with Xamarin Forms

iOS 13's Dark Mode with Xamarin Forms

Copy of YT_Code.png

With iOS 13 came Dark Mode, something a lot of users enjoy, love, plead to developers to include in their apps. So it is only fair to your users that you enable that on your Xamarin Forms applications.

You can watch the video version of this blog post here:

To make sure that you can prepare your application for Dark Mode compatibility, you need two things:

  • Xcode 11

  • Xamarin.iOS 13

The first one is easy, simply update Xcode from the macOS App Store if you haven’t already. The second one should also be easy, but at the moment of this writing, even though iOS 13 is out, the Xamarin team hasn’t caught up, so you need to enable a preview channel from Visual Studio.

Getting Xamarin.iOS 13

If you already have Xamarin.iOS 13 in the stable channel, it means that Xamarin has already released the version to everyone, but if you don’t (as is the case at the moment of this writing), you will have to navigate to the Visual Studio menu on your Mac, select “check for updates” and switch to the Xcode 11 Previews Update channel.

01.png

Simply install any available updates in that channel, and you are good to go.

Creating the themes

Your application will require an additional set of color resources than the default one. So you may probably already have a Resource dictionary for the default colors that you use, but you will now need another resource dictionary to be used when dark mode is on. So if, for example, your default theme has these two colors:

Learn more about XAML resources with my How to design efficiently on Xamarin Forms - Implicit Styles and my How to design efficiently on Xamarin Forms - Explicit Styles blog posts.

<Color x:Key="background">#FFFFFF</Color>
<Color x:Key="mainLabel">#000000</Color>

You will need another set of colors with the same key but different values for dark mode:

<Color x:Key="background">#000000</Color>
<Color x:Key="mainLabel">#FFFFFF</Color>

What I have done to make everything work with dark mode is create, inside the .NET Standard library, a new Styles folder, and inside, I have created (with the ContentPage XAML template) a couple of new classes: LightTheme and DarkTheme. My Styles directory looks like this:

02.png

Now, since using the ContentPage XAML template creates ContentPages, we need to change the class from which these new classes inherit. So both from the C# file and the XAML file we change ContentPage for ResourceDictionary. The C# files then, look like this:

// LightTheme.xaml.cs
public partial class LightTheme : ResourceDictionary
{
    public LightTheme()
    {
        InitializeComponent();
    }
}

// DarkTheme.xaml.cs
public partial class DarkTheme : ResourceDictionary
{
    public DarkTheme()
    {
        InitializeComponent();
    }
}

Similarly, the XAML files now look like this:

<!--LightTheme.xaml-->
<ResourceDictionary
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="DarkMode.Styles.LightTheme">
  
    <Color x:Key="background">#FFFFFF</Color>
    <Color x:Key="mainLabel">#000000</Color>

</ResourceDictionary>

<!--DarkTheme.xaml-->
<ResourceDictionary
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="DarkMode.Styles.DarkTheme">

    <Color x:Key="background">#000000</Color>
    <Color x:Key="mainLabel">#FFFFFF</Color>
    
</ResourceDictionary>

Notice that it is inside these resources where I have defined the colors! Now, at this point, none of this resources is being used within the app, if we were to set, from any content page, the background color to use the background resource like this:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="DarkMode.MainPage"
             BackgroundColor="{DynamicResource background}">

It still wouldn’t use any of those resources.

Note that I am using DynamicResource to reference the color resource, that is because eventually the resource would change from the LightTheme to the DarkTheme as dark mode is enabled/disabled, hence the dynamic nature.

Using the resources

To make any of these resources available to the pages, we need to define them within the Application Resources (inside the App.xaml file):

<Application.Resources>
    <ResourceDictionary Source="/Styles/LightTheme.xaml"/>
</Application.Resources>

By adding that single line of code inside the Application.Resources tag for the App.xaml file, I have set the default resource dictionary to be used within the app (the LightTheme resource dictionary). Now, that background color will work, it will come directly from the LightTheme.xaml file.

Reacting to theme changes

The only thing that is missing now is the ability to respond to theme changes so that the Resource dictionary is set accordingly, instead of always using the default LightTheme as it is right now.

This can be done with the help of a Custom Renderer for ContentPages.

Learn more about custom renderers with my Dynamic-Colored Progress Bars - iOS Custom Renderer in Xamarin Forms and my Changing ListView Selection color with a Custom Renderer blog posts.

Inside the iOS project then, I have created a new Renderers folder, and inside that folder a new PageRenderer C# class. This class has to inherit from PageRenderer and be exported as a renderer for ContentPages. So the class would look like this:

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

[assembly: ExportRenderer(typeof(ContentPage), typeof(DarkMode.iOS.Renderers.PageRenderer))]
namespace DarkMode.iOS.Renderers
{
    public class PageRenderer : Xamarin.Forms.Platform.iOS.PageRenderer
    
        
    

Now, the iOS project exports this PageRenderer, and ContentPages will use it when they get rendered. But it is currently not doing anything. So let's make this renderer call a new SetTheme method every time a ContentPage is created, and also every time dark mode is enabled or disabled within iOS. For this, we need to overload a couple of methods. The first one is OnElementChanged; this will be called when the ContentPage changes (or gets created), and from this method, we will call that new SetTheme method that we will create, like this:

protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
    base.OnElementChanged(e);

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


    try
    {
        SetTheme();
    }
    catch(Exception ex)  

Nothing related to Dark Mode right now, but the next method that we have to override, is related to dark mode. It will be called every time dark mode is enabled or disabled, and from there, we will also need to call SetTheme to decide which ResourceDictionary our app should use. The method is TraitCollectionDidChange, and it will look like this:

public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
{
    base.TraitCollectionDidChange(previousTraitCollection);

    if (TraitCollection.UserInterfaceStyle != previousTraitCollection.UserInterfaceStyle)
        SetTheme();
}

TraitCollection contains the main functionality that we need to work with dark mode on iOS 13, and in here we are evaluating if it indeed changed from its previous value, and if it did, we call SetTheme.

So now all we need to do is create that SetTheme method that we are calling from both overridden methods, and it looks like this:

private void SetTheme()
{
    if (TraitCollection.UserInterfaceStyle == UIUserInterfaceStyle.Dark)
        App.Current.Resources = new DarkTheme(); // needs using DarkMode.Styles;
    else
        App.Current.Resources = new LightTheme();
}

Very simple, right? All we are doing is evaluating whether the TraitCollection’s UI style is dark or not. In each scenario, notice that we are using those DarkTheme or LightTheme ResourceDictionaries that we created at the beginning of the blog post, and we assign it to the App.Current.Resources, which essentially is the Application.Resources that we defined from the App.xaml to use LightTheme by default.

As the dark mode is enabled and disabled, the background for the ContentPage will switch between the LightTheme and DarkTheme resources accordingly.


This topic, along with many many others, is covered in greater depth in my 25-hour long "The Complete Xamarin Developer Course: iOS and Android" course, which you can practically steal from me by

How to connect a cloud SQL database to an Azure Web App Service

How to connect a cloud SQL database to an Azure Web App Service

Routing in Shell -  Passing Information Between Pages

Routing in Shell - Passing Information Between Pages