Forum Xamarin.Forms

Announcement:

The Xamarin Forums have officially moved to the new Microsoft Q&A experience. Microsoft Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

To create new threads and ask questions head over to Microsoft Q&A for .NET and get involved today.

best approach to handle dark theme?

nerdotronnerdotron Member ✭✭

I'm working on an app for UWP, Android, and iOS using Xamarin.Forms.

I just started testing on my iPhone 8 which is set to dark mode and noticed that text in the ListView controls is not visible. The ListView is bound to an ObservableCollection and the custom data template has 2 label controls in a grid. I don't have any color information specified in the XAML, just using defaults. But on the iPhone with dark mode, the list background is black and the text is apparently black as I can't see it. If I switch my iPhone back to light theme I can read the text.

Note that the other controls in the view (outside the list) do not have any problems. I'm using both labels and buttons and they are all readable. Its just the list view which has a black background and the labels within the list view (in the custom data template) apparently have black text on a black background.

My hope would have been that with defaults (no color info set in the XAML) the result would be readable without me having to do anything. Its looking like I will need to have some sort of platform specific code which is also sensitive to theme and then add conditional coloring based on that.

Anyone know of the best, simplest, cross-platform way to achieve this? I'm guessing this will also be an issue for "dark" android and UWP though I haven't tested those.

Answers

  • ColeXColeX Member, Xamarin Team Xamurai

    Both iOS 13 and Android Q now let the user enable Dark Mode for the operating system , we can detect the the mode on each platform and get the result from dependency service .

    Create an interface called IEnvironment and an enum called Theme

    public interface IEnvironment
    {
        Theme GetOperatingSystemTheme();
        Task<Theme> GetOperatingSystemThemeAsync();
    }
    
    public enum Theme { Light, Dark }
    

    Detect it in Application.OnStart and Application.OnResume

     public App : Application
    {
        // ...
    
        protected override async void OnStart()
        {
            base.OnStart();
    
            Theme theme = DependencyService.Get<IEnvironment>().GetOperatingSystemTheme();
    
            SetTheme(theme);
        }
    
        protected override async void OnResume()
        {
            base.OnResume();
    
            Theme theme = DependencyService.Get<IEnvironment>().GetOperatingSystemTheme();
    
            SetTheme(theme);
        }
    
        void SetTheme(Theme theme)
        {
            //Handle Light Theme & Dark Theme
        }
    }
    

    Implement in iOS

    [assembly: Dependency(typeof(Environment_iOS))]
    namespace MyNamespace.iOS
    {
        public class Environment_iOS : IEnvironment
        {
            public Theme GetOperatingSystemTheme()
            {
                //Ensure the current device is running 12.0 or higher, because `TraitCollection.UserInterfaceStyle` was introduced in iOS 12.0
                if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
                {
                    var currentUIViewController = GetVisibleViewController();
    
                    var userInterfaceStyle = currentUIViewController.TraitCollection.UserInterfaceStyle;
    
                    switch (userInterfaceStyle)
                    {
                        case UIUserInterfaceStyle.Light:
                            return Theme.Light;
                        case UIUserInterfaceStyle.Dark:
                            return Theme.Dark;
                        default:
                            throw new NotSupportedException($"UIUserInterfaceStyle {userInterfaceStyle} not supported");
                    }
                }
                else
                {
                    return Theme.Light;
                }
            }
    
            // UIApplication.SharedApplication can only be referenced by the Main Thread, so we'll use Device.InvokeOnMainThreadAsync which was introduced in Xamarin.Forms v4.2.0
            public async Task<Theme> GetOperatingSystemThemeAsync() =>
                Device.InvokeOnMainThreadAsync(GetOperatingSystemTheme);
    
            static UIViewController GetVisibleViewController()
            {
                UIViewController viewController = null;
    
                var window = UIApplication.SharedApplication.KeyWindow;
    
                if (window.WindowLevel == UIWindowLevel.Normal)
                    viewController = window.RootViewController;
    
                if (viewController is null)
                {
                    window = UIApplication.SharedApplication
                        .Windows
                        .OrderByDescending(w => w.WindowLevel)
                        .FirstOrDefault(w => w.RootViewController != null && w.WindowLevel == UIWindowLevel.Normal);
    
                    viewController = window?.RootViewController ?? throw new InvalidOperationException("Could not find current view controller.");
                }
    
                while (viewController.PresentedViewController != null)
                    viewController = viewController.PresentedViewController;
    
                return viewController;
            }
        }
    }
    

    Implement in Android

    using MyNamespace;
    using MyNamespace.Android;
    
    [assembly: Dependency(typeof(Environment_Android))]
    namespace MyNamespace.Android
    {
        public class Environment_Android : IEnvironment
        {
            public Task<Theme> GetOperatingSystemThemeAsync()  =>
                Task.FromResult(GetOperatingSystemTheme());
    
            public Theme GetOperatingSystemTheme()
            {
                //Ensure the device is running Android Froyo or higher because UIMode was added in Android Froyo, API 8.0
                if(Build.VERSION.SdkInt >= BuildVersionCodes.Froyo)
                {
                    var uiModeFlags = CrossCurrentActivity.Current.AppContext.Resources.Configuration.UiMode & UiMode.NightMask;
    
                    switch(uiModelFlags)
                    {
                        case UiMode.NightYes:
                            return Theme.Dark;
    
                        case UiMode.NightNo:
                            return Theme.Light;
    
                        default:
                            throw new NotSupportedException($"UiMode {uiModelFlags} not supported");
                    }
                }
                else
                {
                    return Theme.Light;
                }
            }
        }
    }
    

    Refer

    https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/theming/theming
    https://github.com/brminnick/DarkModeSplashScreen

  • azrinsaniazrinsani Member ✭✭

    Thank you. I noticed that in Android (At least, I'm not sure baout iOS), Dark Mode autmatically inverts the color. So even If i Use say #fff (White), it will invert it to #000, black. Is there a way to disable this inversion? Thanks

  • JoeMankeJoeManke USMember ✭✭✭✭✭

    @azrinsani said:
    Thank you. I noticed that in Android (At least, I'm not sure baout iOS), Dark Mode autmatically inverts the color. So even If i Use say #fff (White), it will invert it to #000, black. Is there a way to disable this inversion? Thanks

    Are you using actual dark mode on an Android 10 (or newer) device, or are you using color inversion from the accessibility settings? Those are two different things.

Sign In or Register to comment.