2

I am having an issue where my Switch seems to only obey the Style set when it is Toggled On. I have set properties for its ThumbColor and OnColor for the two states where it is either Toggled On or Off as follows:

<Style x:Key="SwitchThemeMomentum" TargetType="Switch">
        <Setter Property="IsToggled" Value="{Binding MomentumToggled, Mode=TwoWay}"/>
        <Style.Triggers>
            <DataTrigger TargetType="Switch" Binding="{Binding MomentumToggled, Mode=TwoWay}" Value="True">
                <Setter Property="ThumbColor" Value="{StaticResource Green}"/>
                <Setter Property="OnColor" Value="#CCEDED" />
                <Setter Property="IsToggled" Value="True" />
            </DataTrigger>
            <DataTrigger TargetType="Switch" Binding="{Binding MomentumToggled, Mode=TwoWay}" Value="False">
                <Setter Property="ThumbColor" Value="{StaticResource LightGray}" />
                <Setter Property="OnColor" Value="Red" />
                <Setter Property="IsToggled" Value="False" />
            </DataTrigger>
        </Style.Triggers>
    </Style>

This is how I am calling it:

<Switch
       x:Name="momentumSwitch"
       HorizontalOptions="Start"
       IsToggled="{Binding MomentumToggled, Mode=TwoWay}"
       ScaleX="0.8"
       ScaleY="0.8"
       Style="{StaticResource SwitchThemeMomentum}"
       VerticalOptions="Start" />

ThumbColor is properly obeyed, when I set it to Red I can see it changing, however the same cannot be said for the OnColor only when IsToggled is false; where am I going wrong?

SomeStudent
  • 2,856
  • 1
  • 22
  • 36
  • "OnColor is a Color that affects how the Switch is rendered in the toggled, or on, state." – Jason Sep 10 '20 at 19:48
  • That's what I thought given the name, but it has no "OffColor" property, so I was hoping that maybe it would apply to both. Does this mean the solution involves a renderer/effect? – SomeStudent Sep 10 '20 at 19:50
  • So you want the color to apply to both states? Then there would be no clear visual indicator of Off vs On – Jason Sep 10 '20 at 19:53
  • Yeah that makes sense, oh well, time to make an effect really quick. Makes me wonder why they didn't implement the Off switch color property; or at least make it apply to both settings depending on if it is toggled or not – SomeStudent Sep 10 '20 at 19:56
  • Switch OffColor Enhancement: [OffColor to Switch #10341](https://github.com/xamarin/Xamarin.Forms/issues/10341), [Switch: Added OffColor property #10355](https://github.com/xamarin/Xamarin.Forms/pull/10355) – Benl Sep 11 '20 at 10:43

1 Answers1

1

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.

SomeStudent
  • 2,856
  • 1
  • 22
  • 36