diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Issue33783_SwitchThumbColor_DarkTheme.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Issue33783_SwitchThumbColor_DarkTheme.png new file mode 100644 index 000000000000..b9f9a3e36016 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Issue33783_SwitchThumbColor_DarkTheme.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Issue33783_SwitchThumbColor_LightTheme.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Issue33783_SwitchThumbColor_LightTheme.png new file mode 100644 index 000000000000..7f977a0fbbab Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Issue33783_SwitchThumbColor_LightTheme.png differ diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue33783.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue33783.cs new file mode 100644 index 000000000000..f15fea30b246 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue33783.cs @@ -0,0 +1,61 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 33783, "Switch ThumbColor not applied correctly when theme changes on iOS", PlatformAffected.iOS | PlatformAffected.macOS)] +public class Issue33783 : ContentPage +{ + public Issue33783() + { + var testSwitch = new Switch + { + AutomationId = "TestSwitch", + OnColor = Colors.Green, + ThumbColor = Colors.Red, + IsToggled = true, + HorizontalOptions = LayoutOptions.Center + }; + + var lightThemeButton = new Button + { + AutomationId = "LightThemeButton", + Text = "Light Theme" + }; + + lightThemeButton.Clicked += (s, e) => + { + if (Application.Current != null) + { + Application.Current.UserAppTheme = AppTheme.Light; + } + }; + + var darkThemeButton = new Button + { + AutomationId = "DarkThemeButton", + Text = "Dark Theme" + }; + + darkThemeButton.Clicked += (s, e) => + { + if (Application.Current != null) + { + Application.Current.UserAppTheme = AppTheme.Dark; + } + }; + + var themeButtonsLayout = new HorizontalStackLayout + { + Spacing = 10, + HorizontalOptions = LayoutOptions.Center, + Children = { lightThemeButton, darkThemeButton } + }; + + var contentLayout = new VerticalStackLayout + { + Spacing = 20, + Padding = new Thickness(30), + Children = { testSwitch, themeButtonsLayout } + }; + contentLayout.SetAppThemeColor(BackgroundColorProperty, Colors.White, Colors.Black); + Content = contentLayout; + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/Issue33783_SwitchThumbColor_DarkTheme.png b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/Issue33783_SwitchThumbColor_DarkTheme.png new file mode 100644 index 000000000000..4970767edb25 Binary files /dev/null and b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/Issue33783_SwitchThumbColor_DarkTheme.png differ diff --git a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/Issue33783_SwitchThumbColor_LightTheme.png b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/Issue33783_SwitchThumbColor_LightTheme.png new file mode 100644 index 000000000000..2905f8c2b459 Binary files /dev/null and b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/Issue33783_SwitchThumbColor_LightTheme.png differ diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33783.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33783.cs new file mode 100644 index 000000000000..d3d51d053727 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33783.cs @@ -0,0 +1,34 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue33783 : _IssuesUITest +{ + public Issue33783(TestDevice testDevice) : base(testDevice) + { + } + + public override string Issue => "Switch ThumbColor not applied correctly when theme changes on iOS"; + + [Test, Order(0)] + [Category(UITestCategories.Switch)] + public async Task VerifySwitchThumbColorOnDarkThemeChange() + { + App.WaitForElement("DarkThemeButton"); + App.Tap("DarkThemeButton"); + await Task.Delay(500); + VerifyScreenshot("Issue33783_SwitchThumbColor_DarkTheme"); + } + + [Test, Order(1)] + [Category(UITestCategories.Switch)] + public async Task VerifySwitchThumbColorOnLightThemeChange() + { + App.WaitForElement("LightThemeButton"); + App.Tap("LightThemeButton"); + await Task.Delay(500); + VerifyScreenshot("Issue33783_SwitchThumbColor_LightTheme"); + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue33783_SwitchThumbColor_DarkTheme.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue33783_SwitchThumbColor_DarkTheme.png new file mode 100644 index 000000000000..f72b69a34e23 Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue33783_SwitchThumbColor_DarkTheme.png differ diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue33783_SwitchThumbColor_LightTheme.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue33783_SwitchThumbColor_LightTheme.png new file mode 100644 index 000000000000..88b071a219ad Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue33783_SwitchThumbColor_LightTheme.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/Issue33783_SwitchThumbColor_DarkTheme.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/Issue33783_SwitchThumbColor_DarkTheme.png new file mode 100644 index 000000000000..54a4bd0ba22e Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/Issue33783_SwitchThumbColor_DarkTheme.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/Issue33783_SwitchThumbColor_LightTheme.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/Issue33783_SwitchThumbColor_LightTheme.png new file mode 100644 index 000000000000..7919e36ccf31 Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/Issue33783_SwitchThumbColor_LightTheme.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Issue33783_SwitchThumbColor_DarkTheme.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Issue33783_SwitchThumbColor_DarkTheme.png new file mode 100644 index 000000000000..01d7fc5f105c Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Issue33783_SwitchThumbColor_DarkTheme.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Issue33783_SwitchThumbColor_LightTheme.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Issue33783_SwitchThumbColor_LightTheme.png new file mode 100644 index 000000000000..588f11fc21ff Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Issue33783_SwitchThumbColor_LightTheme.png differ diff --git a/src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs b/src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs index 01fc9c0bb7a7..6fab7dbe3fc1 100644 --- a/src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs +++ b/src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs @@ -3,6 +3,7 @@ using CoreFoundation; using Foundation; using Microsoft.Maui.Graphics; +using Microsoft.Maui.Platform; using ObjCRuntime; using UIKit; using RectangleF = CoreGraphics.CGRect; @@ -69,6 +70,7 @@ class SwitchProxy NSObject? _willEnterForegroundObserver; NSObject? _windowDidBecomeKeyObserver; + IUITraitChangeRegistration? _traitChangeRegistration; public void Connect(ISwitch virtualView, UISwitch platformView) { @@ -83,6 +85,10 @@ public void Connect(ISwitch virtualView, UISwitch platformView) if (PlatformView is not null) { UpdateTrackOffColor(PlatformView); + if (OperatingSystem.IsMacCatalystVersionAtLeast(26)) + { + UpdateThumbColor(PlatformView); + } } }); #elif IOS @@ -95,6 +101,28 @@ public void Connect(ISwitch virtualView, UISwitch platformView) } }); #endif + + // iOS/MacCatalyst 26+ resets ThumbTintColor when theme changes (light/dark mode). + // Register for trait changes to re-apply ThumbColor after UIKit completes its styling. + if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26)) + { + if (_traitChangeRegistration is not null) + { + platformView.UnregisterForTraitChanges(_traitChangeRegistration); + } + + _traitChangeRegistration = platformView.RegisterForTraitChanges( + (IUITraitEnvironment view, UITraitCollection _) => + { + if (view is UISwitch uiSwitch) + { + UpdateThumbColor(uiSwitch); + } + }); + + // iOS 26+ resets ThumbTintColor after initial layout, so re-apply the custom ThumbColor here. + UpdateThumbColor(platformView); + } } // Ensures the Switch track "OFF" color is updated correctly after system-level UI resets. @@ -116,6 +144,22 @@ void UpdateTrackOffColor(UISwitch platformView) }); } + void UpdateThumbColor(UISwitch platformView) + { + DispatchQueue.MainQueue.DispatchAsync(async () => + { + if (VirtualView is null || PlatformView is null) + return; + + await Task.Delay(10); // Small delay, necessary to allow UIKit to complete its internal layout and styling processes before re-applying the custom color + + if (VirtualView is ISwitch view && view.ThumbColor is not null) + { + platformView.UpdateThumbColor(view); + } + }); + } + public void Disconnect(UISwitch platformView) { platformView.ValueChanged -= OnControlValueChanged; @@ -130,6 +174,11 @@ public void Disconnect(UISwitch platformView) NSNotificationCenter.DefaultCenter.RemoveObserver(_windowDidBecomeKeyObserver); _windowDidBecomeKeyObserver = null; } + if (_traitChangeRegistration is not null) + { + platformView.UnregisterForTraitChanges(_traitChangeRegistration); + _traitChangeRegistration = null; + } } void OnControlValueChanged(object? sender, EventArgs e)