Search code examples
iosxamarinmvvmcrossdarkmode

How can I Get Xamarin iOS Application to Automatically Sense Light and Dark Changes?


Someone here (thanks sushihangover!) helped me get my application to read the iOS Settings Dark or Light theme on command. I'm using Xamarin (not Forms). I also need the following (just for iOS):

  1. iOS Settings Theme is Light
  2. App is set to Automatic, so it uses current the iOS Settings Theme (Light)
  3. App launched is Light
  4. Home button press
  5. Change iOS Settings to Dark
  6. Bring app to foreground

App still look Light, but it should look Dark.

I realize the AppDelegate has a WillEnterForeground method, but I don't know how to wire that up so the App looks Dark when it comes to the foreground. I'm using MvvmCross. The following link looks promising.

https://forums.xamarin.com/discussion/181648/best-approach-to-handle-dark-theme

I don't understand how to apply the link's contents to my MvvmCross architecture.

Your help is appreciated!

Thanks! Larry


Solution

  • The best way to react on application changes while using the MVVM pattern would be to implement a IThemeService interface as shown in your link.

    xamarin forms iOS

    But I think it's not possible to react to configuration changes in Xamarin.Forms.iOS plattform while using MvvmCross. I looked into the source code of the MvvmCross.Forms.iOS project and couldn't find any equivalent to the MvvmCross.Forms.Android setup methods like OnConfigurationChanged.

    On Android you can easily refresh the app-theme while change the system theme in the MainActivity.

        public class MainActivity : MvxFormsAppCompatActivity
        {
            public override void OnConfigurationChanged(Configuration newConfig)
            {
                base.OnConfigurationChanged(newConfig);
                this.UpdateTheme(newConfig);
            }
    
            protected override void OnResume()
            {
                base.OnResume();
                UpdateTheme(Resources.Configuration);
            }
    
            protected override void OnStart()
            {
                base.OnStart();
                this.UpdateTheme(Resources.Configuration);
            }
    
            private void UpdateTheme(Configuration newConfig)
            {
                if (Build.VERSION.SdkInt >= BuildVersionCodes.Froyo)
                {
                    var uiModeFlags = newConfig.UiMode & UiMode.NightMask;
                    switch (uiModeFlags)
                    {
                        case UiMode.NightYes:
                            Mvx.IoCProvider.Resolve<IThemeService>().UpdateTheme(BaseTheme.Dark);
                            break;
    
                        case UiMode.NightNo:
                            Mvx.IoCProvider.Resolve<IThemeService>().UpdateTheme(BaseTheme.Light);
                            break;
    
                        default:
                            throw new NotSupportedException($"UiMode {uiModeFlags} not supported");
                    }
                }
            }
        }
    

    But in the AppDelegate on the iOS plattform, you don't have any of these functionalitys to override.

        public class AppDelegate : MvxFormsApplicationDelegate
        {
            public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
            {
                return base.FinishedLaunching(application, launchOptions);
            }
        }
    

    I copied this code from this project.

    native xamarin iOS

    When you are using native iOS you could override the TraitCollectionDidChange method. It's the equivalent to the android OnConfigurationChanged function. Maybee look here for more details. I adapted the android version to iOS for you. At First, you have to create a custom view controller.

    // your supported theme versions
     public enum BaseTheme
        {
            Inherit = 0,
            Light = 1,
            Dark = 2
        }
    
    public class MyViewController : UIViewController
        {
            public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
            {
                base.TraitCollectionDidChange(previousTraitCollection);
    
                if (TraitCollection.UserInterfaceStyle != previousTraitCollection.UserInterfaceStyle)
                {
                    UpdateTheme(TraitCollection.UserInterfaceStyle);
                }
                   
            }
    
            private void UpdateTheme(UIUserInterfaceStyle newConfig)
            {
                switch(newConfig)
                {
                    case UIUserInterfaceStyle.Dark:
                        Mvx.IoCProvider.Resolve<IThemeService>().UpdateTheme(BaseTheme.Dark);
                        break;
    
                    case UIUserInterfaceStyle.Light:
                        Mvx.IoCProvider.Resolve<IThemeService>().UpdateTheme(BaseTheme.Light);
                        break;
    
                    default:
                        throw new NotSupportedException($"UiMode {uiModeFlags} not supported");
                }      
            }
        }
    

    I uploaded a project where I simplify coded an implementation for native IOS and android here. Complete and improve some things and it will work. Also look at the StarWars and TipCalc Project in the mvvmcross sample repo.

    mvvmcross ioc

    your interface structure could look like so; IThemeService (base project) - ThemeService (base project) - ThemeService(iOS project)

    And you have to register the interface of course.

    Mvx.IoCProvider.RegisterSingleton<IThemeService>(() => new ThemeService());