Now that iOS 13 and Android Q allow the user to enable Dark Mode at the operating system level, how can I check for it in Xamarin.Forms?
I've created this in my Xamarin.Forms project, but I'm not sure how to retrieve the values from Xamarin.iOS and Xamarin.Android.
IEnvironment.cs
using System.Threading.Tasks;
namespace MyNamespace
{
public interface IEnvironment
{
Theme GetOperatingSystemTheme();
Task<Theme> GetOperatingSystemThemeAsync();
}
public enum Theme { Light, Dark }
}
App.cs
using Xamarin.Forms;
namespace MyNamespace
{
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
}
}
}
UPDATE as of April 2020:
It is no longer necessary to use platform-specific services to check for light/dark mode in Xamarin.Forms.
We can now get the current theme directly via:
OSAppTheme currentTheme = Application.Current.RequestedTheme;
where the RequestedTheme property returns an OSAppTheme enumeration member: Unspecified, Dark, or Light.
For more info: see documentation and the updated Xamarin.Forms Application.cs code.
We can use the Xamarin.Forms dependency service to access the platform-specific code from iOS and Android.
I've gone into more depth here in this blog post:
https://codetraveler.io/2019/09/10/check-for-dark-mode-in-xamarin-forms/
Xamarin.Forms Code
IEnvironment
using System.Threading.Tasks;
namespace MyNamespace
{
public interface IEnvironment
{
Theme GetOperatingSystemTheme();
Task<Theme> GetOperatingSystemThemeAsync();
}
public enum Theme { Light, Dark }
}
App.cs
using Xamarin.Forms;
namespace MyNamespace
{
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
}
}
}
Xamarin.iOS
using System;
using UIKit;
using Xamarin.Forms;
using MyNamespace;
using MyNamespace.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);
if (window is null)
throw new InvalidOperationException("Could not find current view controller.");
viewController = window.RootViewController;
}
while (viewController.PresentedViewController != null)
viewController = viewController.PresentedViewController;
return viewController;
}
}
}
Xamarin.Android
using System;
using System.Threading.Tasks;
using Android.Content.Res;
using Plugin.CurrentActivity;
using Xamarin.Forms;
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;
}
}
}
}
For iOS:
if (UITraitCollection.CurrentTraitCollection.UserInterfaceStyle == UIUserInterfaceStyle.Dark)
{ ... }
Related
I need to display native controller IOS from Xamarin.forms I have tried this
UIWindow window = UIApplication.SharedApplication.KeyWindow;
UIViewController vc = window.RootViewController;
RGLDocReader.Shared.ShowScanner(vc, HandleRGLDocumentReaderCompletion);
this
How to create navigation in a Xamarin.iOS app?
followed this
https://learn.microsoft.com/en-us/xamarin/ios/app-fundamentals/ios-code-only?tabs=macos
the tutorial works fine, however I need to use view controller in the method as that is specified and once I pass view controller I get null
Actually , it is not a good design to navigate to a native controller in Forms . But if you do want to implement it , you could use DependencyService .
in Forms
Create the interface
public interface IOpenNativeView
{
void OpenNativeView();
}
in iOS project
using xxx.iOS;
using Foundation;
using UIKit;
using Xamarin.Forms;
[assembly: Dependency(typeof(OpenNativeView))]
namespace xxx.iOS
{
public class OpenNativeView : IOpenNativeView
{
void IOpenNativeView.OpenNativeView()
{
var CurrentViewController = topViewControllerWithRootViewController(UIApplication.SharedApplication.Delegate.GetWindow().RootViewController);
CurrentViewController.NavigationController.PushViewController(YourViewController,true);
}
UIViewController topViewControllerWithRootViewController(UIViewController rootViewController)
{
if (rootViewController is UITabBarController)
{
UITabBarController tabBarController = (UITabBarController)rootViewController;
return topViewControllerWithRootViewController(tabBarController.SelectedViewController);
}
else if (rootViewController is UINavigationController)
{
UINavigationController navigationController = (UINavigationController)rootViewController;
return topViewControllerWithRootViewController(navigationController.VisibleViewController);
}
else if (rootViewController.PresentedViewController != null)
{
UIViewController presentedViewController = rootViewController.PresentedViewController;
return topViewControllerWithRootViewController(presentedViewController);
}
else
{
return rootViewController;
}
}
}
}
Now in the ContentPage you can invoke the following line when you want to open the ViewController
DependencyService.Get<IOpenNativeView>().OpenNativeView();
I would like to override the On/Off text of a switch using an Effect rather than a custom renderer.
In Android, I have the following code:
protected override void OnAttached()
{
try
{
if (Control is Android.Widget.Switch control)
{
control.TextOn = "Yes";
control.TextOff = "No";
}
}
OnAttached executes as expected but 'Control' is not an Android.Widget.Switch control but of a related type, e.g. 'android.support.v7.widget.SwitchCompat'. How can I make the code recognize that it should update the text in this case?
Do you want to achieve the result like following effect?
You can achieve it like following code.
using Android.Runtime;
using Android.Support.V7.Widget;
using Android.Views;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ResolutionGroupName("MyCompany")]
[assembly: ExportEffect(typeof(SwitchDemo.Droid.ClickEffect), nameof(SwitchDemo.Droid.ClickEffect))]
namespace SwitchDemo.Droid
{
public class ClickEffect : PlatformEffect
{
protected override void OnAttached()
{
// throw new NotImplementedException();
if (Control is SwitchCompat control)
{
control.ShowText = true;
control.TextOn = "Yes";
control.TextOff = "No";
}
}
protected override void OnDetached()
{
//throw new NotImplementedException();
}
}
}
Here is PCL code.
ClickEffect.cs
public class ClickEffect: RoutingEffect
{
public ClickEffect() : base($"MyCompany.{nameof(ClickEffect)}")
{
}
}
Use it in xaml.
<Switch>
<Switch.Effects>
<local:ClickEffect/>
</Switch.Effects>
</Switch>
I have a xamarin forms application and I have been able to change the navigationbar color. How can I change the statusbar color crossplatform? In the image below you can see the green navigationpagebar background color. Above that it's blue, I want to change the color of that. How can I achieve this crossplatform in xamarin forms?
You could use DependencyService .
in share project , define the interface
public interface IStatusBarColor
{
void SetColoredStatusBar(string color);
}
in Android
Firstly , install the plugin CurrentActivity from nuegt , check https://github.com/jamesmontemagno/CurrentActivityPlugin
using Android.OS;
using Android.Views;
using App24.Droid;
using App24;
using Xamarin.Forms;
using Plugin.CurrentActivity;
[assembly: Dependency(typeof(SetStatusBarColorImplemention))]
namespace App24.Droid
{
public class SetStatusBarColorImplemention : IStatusBarColor
{
public SetStatusBarColorImplemention()
{
}
public void SetColoredStatusBar(string color)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
Device.BeginInvokeOnMainThread(() =>
{
var currentWindow = GetCurrentWindow();
currentWindow.DecorView.SystemUiVisibility = 0;
currentWindow.SetStatusBarColor(Android.Graphics.Color.ParseColor(color));
});
}
}
Window GetCurrentWindow()
{
var window = CrossCurrentActivity.Current.Activity.Window;
window.ClearFlags(WindowManagerFlags.TranslucentStatus);
window.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
return window;
}
}
}
in iOS
using App24;
using App24.iOS;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using ObjCRuntime;
using CoreGraphics;
[assembly: Dependency(typeof(SetStatusBarColorImplemention))]
namespace App24.iOS
{
public class SetStatusBarColorImplemention : IStatusBarColor
{
public void SetColoredStatusBar(string hexColor)
{
if(UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
{
UIWindow window = UIApplication.SharedApplication.KeyWindow;
UIView view = new UIView(window.WindowScene.StatusBarManager.StatusBarFrame);
window.AddSubview(view);
Device.BeginInvokeOnMainThread(() =>
{
if (view.RespondsToSelector(new Selector("setBackgroundColor:")))
{
view.BackgroundColor = Color.FromHex(hexColor).ToUIColor();
}
UIApplication.SharedApplication.SetStatusBarStyle(UIStatusBarStyle.LightContent, false);
topViewControllerWithRootViewController(UIApplication.SharedApplication.KeyWindow.RootViewController).SetNeedsStatusBarAppearanceUpdate();
});
}
else
{
Device.BeginInvokeOnMainThread(() =>
{
UIView statusBar = UIApplication.SharedApplication.ValueForKey(new NSString("statusBar")) as UIView;
if (statusBar.RespondsToSelector(new Selector("setBackgroundColor:")))
{
statusBar.BackgroundColor = Color.FromHex(hexColor).ToUIColor();
}
UIApplication.SharedApplication.SetStatusBarStyle(UIStatusBarStyle.LightContent, false);
topViewControllerWithRootViewController(UIApplication.SharedApplication.KeyWindow.RootViewController).SetNeedsStatusBarAppearanceUpdate();
});
}
}
UIViewController topViewControllerWithRootViewController(UIViewController rootViewController)
{
if (rootViewController is UITabBarController)
{
UITabBarController tabBarController = (UITabBarController)rootViewController;
return topViewControllerWithRootViewController(tabBarController.SelectedViewController);
}
else if (rootViewController is UINavigationController)
{
UINavigationController navigationController = (UINavigationController)rootViewController;
return topViewControllerWithRootViewController(navigationController.VisibleViewController);
}
else if (rootViewController.PresentedViewController != null)
{
UIViewController presentedViewController = rootViewController.PresentedViewController;
return topViewControllerWithRootViewController(presentedViewController);
}
else
{
return rootViewController;
}
}
}
}
Now invoked the line as you want .
DependencyService.Get<IStatusBarColor>().SetColoredStatusBar("#00ff00"); // set the color of bar as green
To my knowledge you need to set the statusbar color on every platform separatly.
There are alot of questions like this here on StackOverflow and Google that can help you with that.
For Android:
check your styles.xml in Resources -> values
look for something like <item name="android:statusBarColor">#000000</item> to set the color
For iOS:
in your AppDelegate.cs look for the FinishedLaunsching-Method.
You can change the Style with UIApplication.SharedApplication.SetStatusBarStyle(UIStatusBarStyle.DarkContent, false);
I have AlertDialog. Bu default the alertDialog is like this:
I want to change the color of OK button and add border color. Is there a solution for this customization .
This is my code:
await Application.Current.MainPage.DisplayAlert("Alert!", " This is DisplayAlert", "OK");
You could use [DependencyService] to call native AlerDialog and change it in specific platforms,here is a simple sample that change the color of the action button .
in Forms ,create the interface:
public interface IPopUp
{
void Popup(string title, string message,Color titleColor,Color messageColor,Color OKButtonColor ,EventHandler handler);
}
in iOS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using App10.iOS;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using App10;
[assembly: Dependency(typeof(PopupImplemention))]
namespace App10.iOS
{
public class PopupImplemention : IPopUp
{
public void Popup(string title, string message, Color titleColor, Color messageColor, Color OKButtonColor, EventHandler handler)
{
UIAlertController alertController = UIAlertController.Create(title,message,UIAlertControllerStyle.Alert);
var firstAttributes = new UIStringAttributes
{
ForegroundColor =titleColor.ToUIColor(),
};
var secondAttributes = new UIStringAttributes
{
ForegroundColor =messageColor.ToUIColor(),
};
alertController.SetValueForKey(new NSAttributedString(title, firstAttributes), new NSString("attributedTitle"));
alertController.SetValueForKey(new NSAttributedString(message, secondAttributes), new NSString("attributedMessage"));
UIAlertAction cancelAction = UIAlertAction.Create("Cancel",UIAlertActionStyle.Cancel,null);
UIAlertAction okAction = UIAlertAction.Create("OK", UIAlertActionStyle.Default,(sender)=> { handler?.Invoke(sender, new EventArgs()) ; });
okAction.SetValueForKey(OKButtonColor.ToUIColor(), new NSString("_titleTextColor"));
alertController.AddAction(cancelAction);
alertController.AddAction(okAction);
var currentViewController = topViewControllerWithRootViewController(UIApplication.SharedApplication.Delegate.GetWindow().RootViewController);
currentViewController.PresentViewController(alertController,true,null);
}
UIViewController topViewControllerWithRootViewController(UIViewController rootViewController)
{
if (rootViewController is UITabBarController)
{
UITabBarController tabBarController = (UITabBarController)rootViewController;
return topViewControllerWithRootViewController(tabBarController.SelectedViewController);
}
else if (rootViewController is UINavigationController)
{
UINavigationController navigationController = (UINavigationController)rootViewController;
return topViewControllerWithRootViewController(navigationController.VisibleViewController);
}
else if (rootViewController.PresentedViewController != null)
{
UIViewController presentedViewController = rootViewController.PresentedViewController;
return topViewControllerWithRootViewController(presentedViewController);
}
else
{
return rootViewController;
}
}
}
}
in Android
in MainActivity
public static MainActivity Intance;
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
Intance = this;
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
using Xamarin.Forms;
using xxx;
using xxx.Droid;
using Android;
using System;
using Xamarin.Forms.Platform.Android;
using Android.Support.V7.App;
using Android.Text;
[assembly: Dependency(typeof(PopupImplemention))]
namespace xxx.Droid
{
public class PopupImplemention : IPopUp
{
public void Popup(string title, string message, Color titleColor, Color messageColor, EventHandler handler)
{
// because html.string could not support format string , so you need to set the color directly in the string with a static value
Android.Support.V7.App.AlertDialog.Builder alert = new Android.Support.V7.App.AlertDialog.Builder(MainActivity.Intance);
alert.SetTitle(title);
alert.SetMessage(message);
alert.SetPositiveButton(Html.FromHtml("<font color='#0000ff'>OK</font>"), (senderAlert, args) =>
{
handler?.Invoke(senderAlert, args);
});
Android.Support.V7.App.AlertDialog dialog = alert.Create();
dialog.Show();
}
}
}
And call it in forms
DependencyService.Get<IPopUp>().Popup("Title","xxxxxxxxxxxx",Color.Red,Color.Blue,Color.Green,(sen,args)=> {
// handle the logic when clikc the OK button
});
You can use cross platform libraries like this one: https://github.com/aritchie/userdialogs
You will need to create a styles.xml and configure it for Android that way. There is currently no way to customize that native control through any of the Xamarin.Forms API's as far as I am aware.
Example:
<style name="AppCompatAlertDialogStyle" parent="Theme.AppCompat.Dialog.Alert">
<item name="colorAccent">#c7ac56</item>
<item name="android:textColor">#c7ac56</item>
<item name="android:background">#5c8487</item>
</style>
Here is a good tutorial on an example on how to do this: http://gmariotti.blogspot.com/2015/04/a-material-styled-alertdialog.html
If you are using Xamarin Android you probably could also hook into the AlertDialog.Builder and set the proprieties pro grammatically: https://developer.android.com/reference/android/app/AlertDialog.Builder
I'm using the following to scroll a list view to the top...
var appointmentGroup = appointmentGroups.First();
appointmentsList.ScrollTo(appointmentGroup.First(), appointmentGroup, ScrollToPosition.Start, true);
This scrolls such that the first item of the first group is at the top of the screen. Except, I want the group's header to be at the top.
This seems slightly crazy but I can't see anyway to do this.
Looking at the source code...
position = templatedItems.GetGlobalIndexForGroup(group) + results.Item2 + 1;
It seems determined to scroll to the item instead of the header.
Shared:
using System;
using Xamarin.Forms;
namespace Infrastructure.UI.Xamarin
{
public class ListViewScroll : ListView
{
public Action ScrollToTopImplementation;
public void ScrollToTop() => ScrollToTopImplementation();
}
}
Android:
using Android.Content;
using Droid.Customization;
using Infrastructure.UI.Xamarin;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(ListViewScroll), typeof(ListViewScrollRenderer))]
namespace Droid.Customization
{
public class ListViewScrollRenderer : ListViewRenderer
{
public ListViewScrollRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
var list = (ListViewScroll) e.NewElement;
list.ScrollToTopImplementation = () =>
Control.SmoothScrollToPosition(0);
}
}
}
iOS: (In order to have a uniform interface in the calling code.)
using Infrastructure.UI.Xamarin;
using iOS.Customization;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ListViewScroll), typeof(ListViewScrollRenderer))]
namespace iOS.Customization
{
public class ListViewScrollRenderer : ListViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
var list = (ListViewScroll) e.NewElement;
list.ScrollToTopImplementation = () =>
Control.ScrollToRow(NSIndexPath.FromRowSection(0, 0), UITableViewScrollPosition.Top, true);
}
}
}