Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Switch] Switch Toggle: OnColor Not set correctly and ThumbColor Not Reset When Toggled Off #19883

Open
MAUIoxo opened this issue Jan 14, 2024 · 10 comments · May be fixed by #20346
Open

[Switch] Switch Toggle: OnColor Not set correctly and ThumbColor Not Reset When Toggled Off #19883

MAUIoxo opened this issue Jan 14, 2024 · 10 comments · May be fixed by #20346
Labels
area-controls-switch Switch area-xaml XAML, CSS, Triggers, Behaviors platform/android 🤖 platform/iOS 🍎 s/needs-attention Issue has more information and needs another look s/triaged Issue has been reviewed s/verified Verified / Reproducible Issue ready for Engineering Triage t/bug Something isn't working
Milestone

Comments

@MAUIoxo
Copy link

MAUIoxo commented Jan 14, 2024

Description

I have a Switch in a Grid within a DataTemplate of a CollectionView with my own ThumbColor defined like in the example below:

<CollectionView.ItemTemplate>
    <DataTemplate x:DataType="{x:Type databaseModels:Band}">
        <Grid ColumnDefinitions="*, Auto" RowDefinitions="Auto" Padding="{OnPlatform Default='5, 0, 5, 0', iOS='5, 0, 5, 15'}">
            <Label Grid.Row="0" Grid.Column="0" Text="{Binding Band.Name}" VerticalOptions="Center" />
            <Switch Grid.Row="0" Grid.Column="1" IsEnabled="True" IsToggled="{Binding IsSelected, Mode=TwoWay}" ThumbColor="{StaticResource Gray100}" OnColor="{StaticResource Orange}" VerticalOptions="Center" HorizontalOptions="Center" Margin="{OnPlatform Android='0, -5, 0, -5'}"/>
        </Grid>
    </DataTemplate>
</CollectionView.ItemTemplate>

Initially the Switch looks like below:

grafik

First of all, when the Switch is toggled, it gets its defined wrong OnColor (should be Orange):

grafik

After that when it is toggled off again, the defined ThumbColor="{StaticResource Gray100}" is not set back and the Switch looks as follows:

grafik

Steps to Reproduce

  1. Define a ThumbColor and an OnColor for a Switch
  2. See how the Color looks like when the Switch is not toggled initially
  3. Toggle the Switch and it will apply the wrong OnColor (should be Orange)
  4. Toggled again to state "Off" and it will not apply the defined ThumbColor again

In the linked example Project from repository:

  1. Launch project
  2. Click on the DotNetBot Icon
  3. Defined ThumbColor is visible
  4. Toggle switch on and color does not change to correct OnColor (should be Orange)
  5. Toggle switch off and color is changed to a system default color, but not the specified ThumbColor again

Hint: The View containing the Switch with the ThumbColor and OnColor is in ..\Pages\Views*BottomSheetContentView.xaml*

Link to public reproduction project repository

ThumbColorNotReset_19883

Version with bug

8.0.6-nightly.9863

Is this a regression from previous behavior?

No, this is something new

Last version that worked well

7.0.101

Affected platforms

iOS, Android

Affected platform versions

No response

Did you find any workaround?

By the time this issue was reported, I did not find a workaround.

On April 13th 2024, I found the simples way that solved it for me, by just using a CustomSwitch that derived from Switch and use this in my XAML which fixed it for me as you can see below.

Relevant log output

No response

@MAUIoxo MAUIoxo added the t/bug Something isn't working label Jan 14, 2024
@MAUIoxo MAUIoxo changed the title [Switch] ThumbColor does not get set when Switch is toggled off [Switch] ThumbColor does not get set back when Switch is toggled off Jan 15, 2024
@MAUIoxo MAUIoxo changed the title [Switch] ThumbColor does not get set back when Switch is toggled off [Switch] Switch Toggle: ThumbColor Not Reset When Toggled Off Jan 15, 2024
@ghost ghost added the legacy-area-controls Label, Button, CheckBox, Slider, Stepper, Switch, Picker, Entry, Editor label Jan 15, 2024
@jsuarezruiz jsuarezruiz added area-xaml XAML, CSS, Triggers, Behaviors s/needs-repro Attach a solution or code which reproduces the issue labels Jan 15, 2024
@ghost
Copy link

ghost commented Jan 15, 2024

Hi @MAUIoxo. We have added the "s/needs-repro" label to this issue, which indicates that we require steps and sample code to reproduce the issue before we can take further action. Please try to create a minimal sample project/solution or code samples which reproduce the issue, ideally as a GitHub repo that we can clone. See more details about creating repros here: https://github.com/dotnet/maui/blob/main/.github/repro.md

This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

@jsuarezruiz
Copy link
Contributor

Could be related? #19882

@MAUIoxo
Copy link
Author

MAUIoxo commented Jan 16, 2024

I added an example project that shows this Bug: ThumbColorNotReset_19883

I don't think that #19882 is related. The Switch is within a Grid, but the other Bug is about Grid.Resources that overwrite globally defined Styles (e.g. for Labels) completely when a property is defined as a Grid.Resource (intention e.g. define a HorizontalOption for all Labels within this Grid) and this is not the same here

@ghost ghost added s/needs-attention Issue has more information and needs another look and removed s/needs-repro Attach a solution or code which reproduces the issue labels Jan 16, 2024
@MAUIoxo MAUIoxo changed the title [Switch] Switch Toggle: ThumbColor Not Reset When Toggled Off [Switch] Switch Toggle: OnColor Not set correctly and ThumbColor Not Reset When Toggled Off Jan 17, 2024
@MAUIoxo
Copy link
Author

MAUIoxo commented Jan 17, 2024

First of all, I fooled myself by not choosing a more distinct OnColor and did not see that also the OnColor is not set! The OnColor should be Orange, but is not assigned. Can also be seen in the same project I attached

@XamlTest XamlTest added s/verified Verified / Reproducible Issue ready for Engineering Triage s/triaged Issue has been reviewed labels Feb 22, 2024
@XamlTest
Copy link

Verified this on Visual Studio Enterprise 17.10.0 Preview 1(8.0.6). Repro on Android 14.0-API34 and iOS 17.2 with below Project:
ThumbColorNotReset.zip

@BlueRaja
Copy link

BlueRaja commented Mar 5, 2024

Related: #20278 #19380

It doesn't appear to be related to Grids. Even the most basic example fails 100% of the time:

<Switch ThumbColor="Red" OnColor="Green"/>

Do you guys not have any integration tests? Or use MAUI internally? This wouldn't have made it past even cursory testing.

@MAUIoxo
Copy link
Author

MAUIoxo commented Mar 9, 2024

That’s also one of my biggest blames that things that used to work „somehow“ got broken and obviously nobody detected it - just the „Clients“ (we). What does that tell us? 😉

@kubaflo kubaflo linked a pull request Mar 17, 2024 that will close this issue
@kubaflo
Copy link
Contributor

kubaflo commented Mar 17, 2024

@MAUIoxo this will probably be fixed in this PR #20346

@MAUIoxo
Copy link
Author

MAUIoxo commented Apr 13, 2024

As also described in #20346 I worked on a solution with a CustomSwitchHandler for iOS which you find below. While doing that, I found out that I just have to use a trivial thing and it worked for me just simply by using a CustomSwitch that derived from Switch. By doing that, I could simply use this CustomSwitch in my XAML and then use the Color settings from OnColor and ThumbColor and it worked.

Here are both ways which were tested with <MauiVersion>8.0.40-nightly.10485</MauiVersion>.


This is the implementation of the CustomSwitchHandler for iOS:


CustomSwitch:

namespace OptimizerApp.Pages.Views.Controls.CustomSwitch
{
    public class CustomSwitch : Switch
    {
        
    }
}

Integration in XAML Code:

...
<constrols:CustomSwitch ... IsToggled="{Binding IsSelected, Mode=TwoWay}" ThumbColor="{StaticResource Gray100}" OnColor="{StaticResource DarkOrange1}" />
...

CustomSwitchHandler:

#if IOS

using OptimizerApp.Pages.Views.Controls.CustomSwitch;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using UIKit;

namespace OptimizerApp.Platforms.iOS.ControlHandlers
{
    public partial class CustomSwitchHandler : ViewHandler<CustomSwitch, UISwitch>
    {
        /// <summary>
        /// Configures property mappings for the CustomSwitch and binds UI properties to platform-specific handlers.
        /// This mapper directs changes in MAUI's CustomSwitch properties to appropriate methods that
        /// update the native iOS UISwitch, ensuring UI consistency and responsiveness:
        /// - 'IsToggled' updates the switch's active state.
        /// - 'OnColor' adjusts the color when the switch is on.
        /// - 'ThumbColor' changes the color of the switch thumb.
        /// These mappings ensure that the visual state of the UISwitch is synchronized with the CustomSwitch.
        /// </summary>
        public static PropertyMapper<CustomSwitch, CustomSwitchHandler> CustomSwitchMapper = new PropertyMapper<CustomSwitch, CustomSwitchHandler>
        {
            [nameof(CustomSwitch.IsToggled)] = MapIsToggled,
            [nameof(CustomSwitch.OnColor)] = MapOnColor,
            [nameof(CustomSwitch.ThumbColor)] = MapThumbColor
        };

        
        public CustomSwitchHandler() : base(CustomSwitchMapper)
        {
        }

        /// <summary>
        /// Creates the native UISwitch view
        /// </summary>
        /// <returns>The newly created UISwitch instance</returns>
        protected override UISwitch CreatePlatformView()
        {
            return new UISwitch();
        }

        /// <summary>
        /// Connects this handler to the UISwitch, setting up event handlers and initial state
        /// </summary>
        /// <param name="platformView">The UISwitch to connect</param>
        protected override void ConnectHandler(UISwitch platformView)
        {
            base.ConnectHandler(platformView);

            platformView.ValueChanged += OnSwitchValueChanged;
            platformView.On = VirtualView.IsToggled;    // Synchronize the UISwitch's state with the IsToggled property of the MAUI CustomSwitch

            SetSwitchColors(platformView, VirtualView);
        }

        /// <summary>
        /// Disconnects this handler from the UISwitch and clean up the event handlers
        /// </summary>
        /// <param name="platformView">The UISwitch to disconnect</param>
        protected override void DisconnectHandler(UISwitch platformView)
        {
            platformView.ValueChanged -= OnSwitchValueChanged;
            base.DisconnectHandler(platformView);

            // Reset UI properties
            ResetSwitchColors(platformView);
        }

        /// <summary>
        /// Sets the ThumbColor and OnTintColor of the UISwitch based on the properties of the CustomSwitch
        /// </summary>
        /// <param name="uiSwitch">The native UISwitch</param>
        /// <param name="customSwitch">The MAUI CustomSwitch</param>
        private static void SetSwitchColors(UISwitch uiSwitch, CustomSwitch customSwitch)
        {
            // Setting the null-values will set it to default values
            uiSwitch.ThumbTintColor = customSwitch.ThumbColor != default(Color) ? customSwitch.ThumbColor.ToPlatform() : null;
            uiSwitch.OnTintColor = customSwitch.OnColor != default(Color) ? customSwitch.OnColor.ToPlatform() : null;
        }

        /// <summary>
        /// Resets the ThumbColor and OnTintColor properties of the UISwitch to defaults
        /// </summary>
        /// <param name="uiSwitch">The UISwitch to reset</param>
        private void ResetSwitchColors(UISwitch uiSwitch)
        {
            uiSwitch.ThumbTintColor = null;
            uiSwitch.OnTintColor = null;
        }

        /// <summary>
        /// Map changes in the OnColor property to the UISwitch
        /// </summary>
        public static void MapOnColor(CustomSwitchHandler handler, CustomSwitch customSwitch)
        {
            if (handler.PlatformView != null)
            {
                handler.PlatformView.OnTintColor = customSwitch.OnColor.ToPlatform();
            }
        }

        /// <summary>
        /// Map changes in the ThumbColor property to the UISwitch
        /// </summary>
        public static void MapThumbColor(CustomSwitchHandler handler, CustomSwitch customSwitch)
        {
            if (handler.PlatformView != null)
            {
                handler.PlatformView.ThumbTintColor = customSwitch.ThumbColor.ToPlatform();
            }
        }

        /// <summary>
        /// Map changes in the IsToggled property to the UISwitch
        /// </summary>
        public static void MapIsToggled(CustomSwitchHandler handler, CustomSwitch customSwitch)
        {
            if (handler.PlatformView != null)
            {
                handler.PlatformView.On = customSwitch.IsToggled;                
                handler.UpdateSwitchColors(handler.PlatformView, customSwitch);     // Update colors when switch is toggled/untoggled
            }
        }

        private void OnSwitchValueChanged(object sender, EventArgs e)
        {
            var uiSwitch = (UISwitch)sender;
            VirtualView.IsToggled = uiSwitch.On;

            UpdateSwitchColors(uiSwitch, VirtualView);
        }

        /// <summary>
        /// Update the OnTintColor and ThumbColor with the colors defined in the customSwitch
        /// </summary>
        private void UpdateSwitchColors(UISwitch uiSwitch, CustomSwitch customSwitch)
        {
            if (uiSwitch.On)
            {
                if (customSwitch.OnColor != default(Color))
                {
                    uiSwitch.OnTintColor = customSwitch.OnColor.ToPlatform();
                }
            }

            if (customSwitch.ThumbColor != default(Color))
            {
                uiSwitch.ThumbTintColor = customSwitch.ThumbColor.ToPlatform();
            }
        }
    }
}

#endif

MauiProgram.cs:

...
#if IOS
using OptimizerApp.Platforms.iOS.ControlHandlers;
#endif
...

var builder = MauiApp.CreateBuilder();
builder
    .UseMauiApp<App>()
    ...
    .ConfigureMauiHandlers(handlers =>
    {
        // The handler will only be called if the target platform is iOS
#if IOS
        handlers.AddHandler<CustomSwitch, CustomSwitchHandler>();
#endif
    });
...

Result:

CustomSwitchHandler



BUT:
As mentioned above, it even does not have to be this full blown implementation. As I found out, it was sufficient to just use the simple CustomSwitch above which simply derives from Switch and use this in XAML with corresponding Color settings.

Here are the before and after versions on iOS and Android :


Simple Switch iOS:

SwitchiOS


CustomSwitch iOS:

CustomSwitchiOS



Simple Switch Android:

SwitchAndroid


CustomSwitch Android:

CustomSwitchHandlerAndroid


@Eilon Eilon removed the legacy-area-controls Label, Button, CheckBox, Slider, Stepper, Switch, Picker, Entry, Editor label May 10, 2024
@jsuarezruiz jsuarezruiz added this to the Backlog milestone Jun 10, 2024
@samhouts samhouts removed s/verified Verified / Reproducible Issue ready for Engineering Triage s/triaged Issue has been reviewed labels Jul 3, 2024
@samhouts samhouts added the s/verified Verified / Reproducible Issue ready for Engineering Triage label Jul 10, 2024
@pierre01
Copy link

I have the same issue wit Android

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-controls-switch Switch area-xaml XAML, CSS, Triggers, Behaviors platform/android 🤖 platform/iOS 🍎 s/needs-attention Issue has more information and needs another look s/triaged Issue has been reviewed s/verified Verified / Reproducible Issue ready for Engineering Triage t/bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants