Controlando el modo oscuro de iOS 13 en Xamarin.Forms

Antes de comenzar, comentaos que este post forma parte de una gran iniciativa ideada por Luis Beltrán (mi agradecimiento desde aquí). Y no es otra que un calendario de adviento en el que colaboramos la comunidad de Xamarin escribiendo un post o haciendo un video, cada día desde el 1 hasta el 28 de Diciembre. En este enlace, podéis ver todo el contenido que se ha generado para este calendario https://www.luisbeltran.mx/2019/11/06/primer-calendario-de-adviento-de-xamarin-en-espanol/

Un vez hecha la introducción, vamos a por el post que nos ocupa.

Con la llegada de iOS 13, ha llegado a nuestros dispositivos el modo oscuro, por lo que, si queremos que nuestra app quede integrada con este nuevo modo, tenemos que hacer ciertas modificaciones. Hay controles q cambian su aspecto sin tener que hacer nada. Por ejemplo, un entry tiene su fondo de color blanco  cuando nuestro dispositivo está en modo claro y, en cambio, su fondo es negro con el modo oscuro.

A continuación os muestro el ejemplo de una aplicación de ejemplo que he realizado.

Dark mode sample

Como se puede apreciar, tiene varios elementos que cambian dependiendo del modo (una image, un label y un entry) y otros que no se ven alterados por el cambio de modo (un botón que siempre se ve de color rojo).

Para conseguirlo, he creado un archivo de estilo DefaultTheme, que es un diccionario de recursos en el que se almacenan los estilos comunes tanto en el modo claro  como el oscuro.

<?xml version="1.0" encoding="UTF-8"?>
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="XamarinDarkMode.Styles.DefaultTheme">
    <Color x:Key="ButtonColor">#FF0000</Color>
</ResourceDictionary>

Para definir los estilos de cada modo, se han creado dos archivos, DarkTheme y LightTheme. Ambos son diccionarios de recursos cuyo source es DefaultTheme.

<?xml version="1.0" encoding="UTF-8"?>
<?ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="XamarinDarkMode.Styles.DarkTheme"
                    Source="DefaultTheme.xaml">
    <?Color x:Key="BackgroundColor">#000000<?/Color>
    <?Color x:Key="LabelColor">#FFFFFF<?/Color>
    <?FileImageSource x:Key="CloudImageSource">lightCloud.png<?/FileImageSource>
<?/ResourceDictionary>
<?xml version="1.0" encoding="UTF-8"?>
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="XamarinDarkMode.Styles.LightTheme"
                    Source="DefaultTheme.xaml">
    <Color x:Key="BackgroundColor">#FFFFFF</Color>
    <Color x:Key="LabelColor">#000000</Color>
    <FileImageSource x:Key="CloudImageSource">darkCloud.png</FileImageSource>
</ResourceDictionary>

Una vez añadidos los recursos a los archivos anteriores, estamos en disposición de utilizarlos en nuestras páginas. Los recursos que sean comunes en ambos modos, se puede usar como StaticResource, en cambio, los que varíen por cada modo, hay que usarlos como DynamicResource, ya que pueden cambiar en tiempo de ejecución si el usuario cambia el modo del dispositivo. En nuestro ejemplo, la página que tenemos sería la siguiente.

<?xml version="1.0" encoding="utf-8"?>
<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="XamarinDarkMode.MainPage"
             BackgroundColor="{DynamicResource BackgroundColor}">
    <StackLayout VerticalOptions="Center" Orientation="Vertical"
                 Margin="20,0">
        <Image Source="{DynamicResource CloudImageSource}" HeightRequest="200" Aspect="AspectFit"/>
        <Label Text="Welcome to Xamarin.Forms!" TextColor="{DynamicResource LabelColor}"/>
        <Entry Placeholder="Entry placeholder" />
        <Button Text="Button" TextColor="{StaticResource ButtonColor}"/>
    </StackLayout>
</ContentPage>

En el ejemplo anterior, el color del botón es siempre rojo, para los dos modos, el resto de elementos variarán.

Por otro lado, es necesario modificar el App.xaml para indicarle el estilo que se tiene que aplicar. Para iOS, no importa demasiado el estilo que le indiquemos aquí ya que, tal y como se muestra más adelante, esté se aplica para cada página, al cargarse y al cambiar el modo.

<?xml version="1.0" encoding="utf-8"?>
<Application 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="XamarinDarkMode.App">
    <Application.Resources>
        <ResourceDictionary Source="/Styles/LightTheme.xaml"/>
    </Application.Resources>
</Application>

 

Por último, para aplicar el estilo que corresponda al arrancar la aplicación y detectar cambios en el modo, realizados por el usuario mientras nuestra app está abierta, tenemos que extender la funcionalidad de PageRenderer, tal y como se muestra a continuación.

[assembly: Xamarin.Forms.ExportRenderer(typeof(Xamarin.Forms.ContentPage), typeof(XamarinDarkMode.iOS.Renderers.PageRenderer))]
namespace XamarinDarkMode.iOS.Renderers
{
    using System;
    using UIKit;
    using Xamarin.Forms.Platform.iOS;
    using XamarinDarkMode.Styles;

    public class PageRenderer : Xamarin.Forms.Platform.iOS.PageRenderer
    {
        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);
            if (e.OldElement != null)
                return;

            if (Element == null)
                return;

            try
            {
                SetTheme();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"Error changing theme. {ex?.Message}");
            }
        }

        public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
        {
            base.TraitCollectionDidChange(previousTraitCollection);
            if (TraitCollection.UserInterfaceStyle != previousTraitCollection.UserInterfaceStyle)
                SetTheme();
        }

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

 

Como es habitual, he creado un proyecto en GitHub para ofrecer más detalles de la implementación que he realizado. Espero que os sea de ayuda https://github.com/jorgediegocrespo/XamarinDarkMode

2 comentarios sobre “Controlando el modo oscuro de iOS 13 en Xamarin.Forms

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios .