Thanks to some googling, and writing a custom renderer:
Change color of UISwitch in "off" state
https://forums.xamarin.com/discussion/62450/how-can-i-customize-the-color-in-switch
and
https://blog.wislon.io/posts/2017/05/15/xamforms-change-switch-colour
The solution is as follows: Create new CustomSwitch
control which will add a new Color
property called OffColor
as so:
public class CustomSwitch : Switch
{
public static readonly BindableProperty OffColorProperty =
BindableProperty.Create(nameof(CustomSwitch), typeof(Color), typeof(CustomSwitch));
public CustomSwitch()
{
OffColor = Color.Transparent;
}
public Color OffColor {
get => (Color) GetValue(OffColorProperty);
set => SetValue(OffColorProperty, value);
}
}
Follow this app by creating both an Android and iOS renderer:
Android:
public class CustomSwitchRenderer : SwitchRenderer
{
public CustomSwitchRenderer(Context context): base(context) { }
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if(Control == null)
{
return;
}
var element = (CustomSwitch) Element;
if(!element.IsToggled)
{
Control.TrackTintList = ColorStateList.ValueOf(element.OffColor.ToAndroid());
}
}
}
iOS:
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if(Control == null)
{
return;
}
var element = (CustomSwitch) Element;
var maskView = new UIView(Control.Frame)
{
BackgroundColor = element.IsToggled ? element.OnColor.ToUIColor() : element.OffColor.ToUIColor(),
ClipsToBounds = true
};
maskView.Layer.CornerRadius = Control.Frame.Height / 2;
Control.MaskView = maskView;
if(!element.IsToggled)
{
Control.TintColor = element.OffColor.ToUIColor();
Control.BackgroundColor = element.OffColor.ToUIColor();
} else
{
Control.TintColor = element.OnColor.ToUIColor();
Control.OnTintColor = element.OnColor.ToUIColor();
Control.BackgroundColor = element.OnColor.ToUIColor();
}
}
iOS is a bit more involved since you pretty much have to create the Android equivalent of the TrackTintList because for iOS all that TintColor does is just apply the color to the border of the switch. The mask is there to fill it in accordingly.
Finally, maintaining the spirit of using a Style resource dictionary, update the style as follows:
<Style x:Key="SwitchThemeMomentum" TargetType="customcontrols:CustomSwitch">
<Setter Property="IsToggled" Value="{Binding MomentumToggled, Mode=TwoWay}"/>
<Style.Triggers>
<DataTrigger TargetType="customcontrols:CustomSwitch" Binding="{Binding MomentumToggled, Mode=TwoWay}" Value="True">
<Setter Property="ThumbColor" Value="{StaticResource Green}"/>
<Setter Property="OffColor" Value="Transparent" />
<Setter Property="OnColor" Value="{StaticResource SwitchOn}" />
<Setter Property="IsToggled" Value="True" />
</DataTrigger>
<DataTrigger TargetType="customcontrols:CustomSwitch" Binding="{Binding MomentumToggled, Mode=TwoWay}" Value="False">
<Setter Property="ThumbColor" Value="{StaticResource LightGray}" />
<Setter Property="OffColor" Value="{StaticResource Gray80}" />
<Setter Property="IsToggled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
Important: Note how we update the TargetType
to be our CustomSwitch
instead of a default Switch
; this is because the default Switch
control obviously lacks a OffColor
property.