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

Fixed Switch Thumb and On Colors #20346

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

kubaflo
Copy link
Contributor

@kubaflo kubaflo commented Feb 3, 2024

Description of Change

The initial respect for the ThumbColor and OnColor properties was
overridden by values set within visual states each time the switcher changed
its state. I recommend verifying whether these values have been overwritten,
and if so, exclusively utilizing the new properties.

Issues Fixed

Fixes #19883

Fixes #19380

Before
Simulator.Screen.Recording.-.iPhone.15.Pro.Max.-.2024-02-03.at.02.06.58.mp4
After
Screen.Recording.2024-02-04.at.00.25.46.mov

The initial respect for the ThumbColor and OnColor properties was
overridden by values set within visual states each time the switcher changed
its state. I recommend verifying whether these values have been overwritten,
and if so, exclusively utilizing the new properties.
@kubaflo kubaflo requested a review from a team as a code owner February 3, 2024 23:45
@ghost ghost added the community ✨ Community Contribution label Feb 3, 2024
@ghost
Copy link

ghost commented Feb 3, 2024

Hey there @kubaflo! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@kubaflo kubaflo force-pushed the onColor-thumbColor-switcher-fix branch from 850ee01 to 627ee31 Compare February 3, 2024 23:52
@jsuarezruiz
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@ghost ghost added the legacy-area-controls Label, Button, CheckBox, Slider, Stepper, Switch, Picker, Entry, Editor label Feb 5, 2024
@BlueRaja
Copy link

BlueRaja commented Mar 5, 2024

Any reason this has been sitting unmerged for a month? Seems like a pretty basic and critical bug to be sitting unfixed for months now

@rmarinho
Copy link
Member

rmarinho commented Mar 5, 2024

/azp run MAUI-UITests-public

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@MAUIoxo
Copy link

MAUIoxo commented Mar 19, 2024

@jsuarezruiz @rmarinho
Just wanted to ask why this has not been merged to main for such a long time now? Is there anything missing? Still waiting for this to be fixed. And this fix was provided long time ago. Any reason for that?

@rmarinho
Copy link
Member

@MAUIoxo we are not sure this is correct fix to work with VSM. we were discussing internally.

@MAUIoxo
Copy link

MAUIoxo commented Mar 20, 2024

Thanks for the update on this! So, at least we know why it is parked 😊

@elparasite
Copy link

@MAUIoxo we are not sure this is correct fix to work with VSM. we were discussing internally.

Hi, was the discussion effective ?
This is an unbearable suspense.

@MAUIoxo
Copy link

MAUIoxo commented Apr 13, 2024

@MAUIoxo we are not sure this is correct fix to work with VSM. we were discussing internally.

Hi, was the discussion effective ? This is an unbearable suspense.

Yes it was - I found out that this issue is going to take forever to be fixed and probably no one is going to deal with a solution anytime soon, so I guess I'll just have to come up with my own solution 😅

Here is an example of a CustomSwitch with a CustomSwitchHandler that allows me to set the OnColor and ThumbColor as in a normal Switch in XAML and map the states of IsToggled, OnColor and ThumbColor to the corresponding properties of the newly created iOS UISwitch. This is just a possible solution for iOS. I've tested it with scrolling also as re-drawing View elements also has to be updated correspondingly. At least it works for me and tested with <MauiVersion>8.0.40-nightly.10485</MauiVersion>.


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
    });
...

CustomSwitchHandler



BUT:
It even does not have to be this full blown implementation. As I found out, it was sufficient to just use this simple CustomSwitch above which simply derives from Switch and use this in XAML with corresponding Color settings. See before and after 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
7 participants