Skip to content

[iOS] Fix Switch custom colors on iOS 26#35385

Open
AdamEssenmacher wants to merge 9 commits into
dotnet:inflight/currentfrom
AdamEssenmacher:issue-35257
Open

[iOS] Fix Switch custom colors on iOS 26#35385
AdamEssenmacher wants to merge 9 commits into
dotnet:inflight/currentfrom
AdamEssenmacher:issue-35257

Conversation

@AdamEssenmacher
Copy link
Copy Markdown

@AdamEssenmacher AdamEssenmacher commented May 11, 2026

Description of Change

Fixes iOS 26 Switch custom color rendering by using UIKit's public classic sliding switch style when MAUI switch colors are customized.

On iOS 26, the automatic/liquid UISwitch style can ignore MAUI OffColor/OnColor track tinting and ThumbColor. This change opts switches with a non-null MAUI track or thumb color into UISwitchStyle.Sliding on iOS 26+, while leaving unstyled switches as UISwitchStyle.Automatic to preserve the native default appearance.

This also:

  • Reapplies colors after UIKit lifecycle/style changes that can rebuild or reset internal switch views.
  • Re-arms color reapply after window reattachment.
  • Clears native ThumbTintColor when ThumbColor is reset to null.
  • Keeps the existing Issue35257 Appium visual regression coverage from [iOS 26] Fix Switch ThumbColor and OffColor not applied on initial load #35400 and adds focused iOS handler tests for the style, reset, theme-change, and reattachment paths.

Conflict resolution:

Validation performed:

  • dotnet build src/Core/src/Core.csproj -f net10.0-ios26.0 -c Debug --no-restore
  • dotnet build src/Controls/tests/TestCases.iOS.Tests/Controls.TestCases.iOS.Tests.csproj -c Debug --no-restore

Issues Fixed

Fixes #35257

Copilot AI review requested due to automatic review settings May 11, 2026 19:36
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 11, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35385

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35385"

@dotnet-policy-service dotnet-policy-service Bot added the community ✨ Community Contribution label May 11, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses an iOS 26 regression where UISwitch can ignore MAUI Switch custom colors by opting into UIKit’s classic sliding switch style (UISwitchStyle.Sliding) when MAUI track/thumb colors are set, while leaving default (unstyled) switches on UISwitchStyle.Automatic.

Changes:

  • Updates iOS UISwitch color mapping to switch styles on iOS 26+ when colors are customized, and reapply colors after style changes.
  • Ensures native ThumbTintColor is cleared when MAUI ThumbColor is reset to null, and makes Switch.ThumbColor notify the handler on change.
  • Adds iOS device-handler tests and a new Appium visual regression issue test/page for iOS 26 switch custom colors.

Reviewed changes

Copilot reviewed 6 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Core/src/Platform/iOS/SwitchExtensions.cs Applies iOS 26+ preferred style switching and re-applies track/thumb colors when UIKit may rebuild internal views.
src/Controls/src/Core/Switch/Switch.cs Adds a ThumbColor bindable-property changed callback to update the handler mapping.
src/Core/tests/DeviceTests/Handlers/Switch/SwitchHandlerTests.iOS.cs Adds iOS 26 style-selection assertions and validates thumb tint clearing behavior.
src/Controls/tests/TestCases.HostApp/MauiProgram.cs Allows iOS HostApp startup-page selection via the test environment variable (previously MacCatalyst-only).
src/Controls/tests/TestCases.HostApp/Issues/Issue35257.cs Adds a HostApp repro page with default vs custom-colored switches for visual validation.
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35257.cs Adds an iOS Appium screenshot test to validate initial custom color rendering.

Comment thread src/Core/src/Platform/iOS/SwitchExtensions.cs Outdated
@MauiBot MauiBot added s/agent-review-incomplete s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels May 11, 2026
Comment thread src/Controls/src/Core/Switch/Switch.cs Outdated
Comment thread src/Core/src/Platform/iOS/SwitchExtensions.cs
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some tests are failing

Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there some test failures caused by dark/light themes

@dotnet dotnet deleted a comment from MauiBot May 12, 2026
@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues and removed s/agent-review-incomplete labels May 12, 2026
@dotnet dotnet deleted a comment from MauiBot May 14, 2026
@MauiBot MauiBot added s/agent-review-incomplete and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels May 14, 2026
PureWeen added a commit that referenced this pull request May 18, 2026
- SKILL.md platform table: add /Handlers/*/iOS/, /Handlers/*/MacCatalyst/,
  and /Handlers/*/Windows/ to platform rows. Mirrors the Android row's
  handler-subdirectory pattern. iOS-directory row maps to platform/ios
  ONLY (not dual with platform/macos) because handler /iOS/ directories
  compile for iOS TFM only, unlike the *.iOS.cs file-extension pattern
  which compiles for both iOS and MacCatalyst.

- eval.yaml PR #35461 scenario: rename to flag scope-restriction intent,
  add platform/android positive assertion (the PR touches Android files)
  and forbidden-label negatives for i/regression, partner/syncfusion,
  t/bug — those labels already exist on the PR but our labeler must NOT
  apply them.

- eval.yaml PR #35385 scenario: add platform/macos and platform/windows
  assertions. The PR touches Platform/Windows/, Platform/Android/, and
  *.iOS.cs files — that last one triggers BOTH platform/ios AND
  platform/macos per our file-extension rule.

- eval.yaml XAML scenario: rename 'issue' -> 'PR' (prompt targets a PR).

- workflow.md frontmatter description: update from generic
  'appropriate labels chosen from the existing repository label set'
  to explicitly state 'area-* and platform/* ONLY, does NOT apply
  triage, status, priority, type, severity, partner, regression, or
  any other label families'. Locked-yml regenerated by gh aw compile.

Adversarial review findings deliberately NOT applied:
- (?i) regex prefix: invalidated — skill-validator already passes
  RegexOptions.IgnoreCase and StringComparison.OrdinalIgnoreCase, so
  case is handled at the framework level.
- output_not_contains 'area-' / 'platform/' on noop scenarios: too
  risky — agent prose may legitimately reference these prefixes when
  explaining why no labels apply.
- Issue #35448 prompt change: existing-label contamination is a
  framework limitation (substring match in prose); not worth a
  scenario-level fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented May 25, 2026

/review -b feature/refactor-copilot-yml -p ios

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against #2 automatically generated candidates and selected try-fix-2 as the strongest fix.

Why: try-fix-2 passed the iOS SwitchHandler regression tests and provides the same iOS 26 custom-color behavior with less persistent state than the PR fix. It avoids queued reapply state and duplicate trait handling by materializing the Sliding-style subview hierarchy before applying colors, then relying on idempotent lifecycle reapply as a safety net.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (`try-fix-2`)
diff --git a/src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs b/src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs
index 01fc9c0bb7..42109f5ee8 100644
--- a/src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs
+++ b/src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs
@@ -1,8 +1,7 @@
 using System;
-using System.Threading.Tasks;
-using CoreFoundation;
 using Foundation;
 using Microsoft.Maui.Graphics;
+using Microsoft.Maui.Platform;
 using ObjCRuntime;
 using UIKit;
 using RectangleF = CoreGraphics.CGRect;
@@ -21,7 +20,7 @@ namespace Microsoft.Maui.Handlers
 
 		protected override UISwitch CreatePlatformView()
 		{
-			return new UISwitch(RectangleF.Empty);
+			return new MauiSwitch(RectangleF.Empty);
 		}
 
 		protected override void ConnectHandler(UISwitch platformView)
@@ -74,51 +73,35 @@ namespace Microsoft.Maui.Handlers
 			{
 				_virtualView = new(virtualView);
 				_platformView = new(platformView);
+				(platformView as MauiSwitch)?.Connect(virtualView);
 				platformView.ValueChanged += OnControlValueChanged;
 
 #if MACCATALYST
 				_windowDidBecomeKeyObserver = NSNotificationCenter.DefaultCenter.AddObserver(
 					new NSString("NSWindowDidBecomeKeyNotification"), _ =>
 					{
-						if (PlatformView is not null)
+						if (PlatformView is not null && VirtualView is ISwitch view)
 						{
-							UpdateTrackOffColor(PlatformView);
+							PlatformView.UpdateTrackColor(view);
+							PlatformView.UpdateThumbColor(view);
 						}
 					});
 #elif IOS
 				_willEnterForegroundObserver = NSNotificationCenter.DefaultCenter.AddObserver(
 					UIApplication.WillEnterForegroundNotification, _ =>
 					{
-						if (PlatformView is not null)
+						if (PlatformView is not null && VirtualView is ISwitch view && view.TrackColor is not null)
 						{
-							UpdateTrackOffColor(PlatformView);
+							PlatformView.UpdateTrackColor(view);
 						}
 					});
 #endif
 			}
 
-			// Ensures the Switch track "OFF" color is updated correctly after system-level UI resets.
-			// This is necessary because UIKit may re-apply default styles to internal views during certain lifecycle events,
-			// especially when the app enters the background and returns to the foreground.
-			void UpdateTrackOffColor(UISwitch platformView)
-			{
-				DispatchQueue.MainQueue.DispatchAsync(async () =>
-				{
-					if (!platformView.On)
-					{
-						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.TrackColor is not null)
-						{
-							platformView.UpdateTrackColor(view);
-						}
-					}
-				});
-			}
-
 			public void Disconnect(UISwitch platformView)
 			{
 				platformView.ValueChanged -= OnControlValueChanged;
+				(platformView as MauiSwitch)?.Disconnect();
 
 				if (_willEnterForegroundObserver is not null)
 				{
diff --git a/src/Core/src/Platform/iOS/MauiSwitch.cs b/src/Core/src/Platform/iOS/MauiSwitch.cs
index e56ce5bb7b..f156730447 100644
--- a/src/Core/src/Platform/iOS/MauiSwitch.cs
+++ b/src/Core/src/Platform/iOS/MauiSwitch.cs
@@ -1,16 +1,15 @@
 using System;
-using CoreFoundation;
 using CoreGraphics;
 using UIKit;
 
 namespace Microsoft.Maui.Platform
 {
+	// Stateless subclass: lifecycle hooks perform a single readiness-gated synchronous
+	// re-application of TrackColor/ThumbColor. No queued state, no deferred dispatch.
+	// Re-applying the same colors is idempotent, so no re-entry guard is needed.
 	internal class MauiSwitch : UISwitch
 	{
 		WeakReference<ISwitch>? _virtualView;
-		bool _colorReapplyQueued;
-		bool _isReapplyingColors;
-		bool _needsColorReapply;
 
 		public MauiSwitch(CGRect frame) : base(frame)
 		{
@@ -18,21 +17,12 @@ namespace Microsoft.Maui.Platform
 
 		public void Connect(ISwitch virtualView)
 		{
-			_virtualView = new(virtualView);
-			SetNeedsColorReapply();
+			_virtualView = new WeakReference<ISwitch>(virtualView);
 		}
 
 		public void Disconnect()
 		{
 			_virtualView = null;
-			_needsColorReapply = false;
-		}
-
-		public void SetNeedsColorReapply()
-		{
-			_needsColorReapply = true;
-			SetNeedsLayout();
-			QueueColorReapply();
 		}
 
 		public override void MovedToWindow()
@@ -40,62 +30,39 @@ namespace Microsoft.Maui.Platform
 			base.MovedToWindow();
 			if (Window is not null)
 			{
-				SetNeedsColorReapply();
+				TryApplyColors();
 			}
 		}
 
 		public override void LayoutSubviews()
 		{
 			base.LayoutSubviews();
-			QueueColorReapply();
+			TryApplyColors();
 		}
 
 		public override void TraitCollectionDidChange(UITraitCollection? previousTraitCollection)
 		{
 			base.TraitCollectionDidChange(previousTraitCollection);
-			SetNeedsColorReapply();
+			TryApplyColors();
 		}
 
-		void QueueColorReapply()
+		void TryApplyColors()
 		{
-			if (_colorReapplyQueued || !_needsColorReapply)
+			if (_virtualView is null || !_virtualView.TryGetTarget(out var view))
 			{
 				return;
 			}
 
-			_colorReapplyQueued = true;
-
-			DispatchQueue.MainQueue.DispatchAsync(() =>
-			{
-				_colorReapplyQueued = false;
-				TryReapplyColors();
-			});
-		}
-
-		void TryReapplyColors()
-		{
-			if (_isReapplyingColors || !_needsColorReapply || !this.IsReadyForColorReapply())
+			// Gate on readiness: track subview must exist and have non-zero bounds,
+			// otherwise the BackgroundColor set in ApplyTrackColor would land on
+			// the wrong subview (the root cause of the iOS 26+ initial-render bug).
+			if (!this.IsReadyForColorReapply())
 			{
 				return;
 			}
 
-			if (_virtualView is null || !_virtualView.TryGetTarget(out var virtualView))
-			{
-				return;
-			}
-
-			_isReapplyingColors = true;
-
-			try
-			{
-				this.ApplyTrackColor(virtualView);
-				this.ApplyThumbColor(virtualView);
-				_needsColorReapply = false;
-			}
-			finally
-			{
-				_isReapplyingColors = false;
-			}
+			this.ApplyTrackColor(view);
+			this.ApplyThumbColor(view);
 		}
 	}
 }
diff --git a/src/Core/src/Platform/iOS/SwitchExtensions.cs b/src/Core/src/Platform/iOS/SwitchExtensions.cs
index 0133b681f7..100de0ff7f 100644
--- a/src/Core/src/Platform/iOS/SwitchExtensions.cs
+++ b/src/Core/src/Platform/iOS/SwitchExtensions.cs
@@ -19,6 +19,18 @@ namespace Microsoft.Maui.Platform
 				return;
 			}
 
+			// Switch to Sliding style first (iOS/MacCatalyst 26+ only, and only when custom
+			// colors are present). Forcing synchronous layout materializes the new style's
+			// track subview tree BEFORE we reach into Subviews — which is the root cause
+			// of the iOS 26+ initial-render bug (we used to set BackgroundColor on a
+			// subview that hadn't been materialized for the Sliding style yet).
+			uiSwitch.UpdatePreferredStyleAndMaterialize(view);
+
+			uiSwitch.ApplyTrackColor(view);
+		}
+
+		internal static void ApplyTrackColor(this UISwitch uiSwitch, ISwitch view)
+		{
 			var uIView = GetTrackSubview(uiSwitch);
 
 			if (uIView is null)
@@ -62,9 +74,61 @@ namespace Microsoft.Maui.Platform
 			if (view == null)
 				return;
 
-			Graphics.Color thumbColor = view.ThumbColor;
-			if (thumbColor != null)
-				uiSwitch.ThumbTintColor = thumbColor?.ToPlatform();
+			uiSwitch.UpdatePreferredStyleAndMaterialize(view);
+			uiSwitch.ApplyThumbColor(view);
+		}
+
+		internal static void ApplyThumbColor(this UISwitch uiSwitch, ISwitch view)
+		{
+			uiSwitch.ThumbTintColor = view.ThumbColor?.ToPlatform();
+		}
+
+		// Switches to UISwitchStyle.Sliding on iOS/MacCatalyst 26+ when custom colors are
+		// requested, then synchronously forces UIKit to materialize the new style's
+		// subview tree via LayoutIfNeeded. This avoids any need for deferred reapply
+		// queues: colors land on the correct (now-materialized) subview on the first try.
+		static void UpdatePreferredStyleAndMaterialize(this UISwitch uiSwitch, ISwitch view)
+		{
+#if IOS || MACCATALYST
+			if (!IsSlidingStyleRequiredForCustomColors())
+			{
+				return;
+			}
+
+			var preferredStyle = view.TrackColor is not null || view.ThumbColor is not null
+				? UISwitchStyle.Sliding
+				: UISwitchStyle.Automatic;
+
+			if (uiSwitch.PreferredStyle != preferredStyle)
+			{
+				uiSwitch.PreferredStyle = preferredStyle;
+				uiSwitch.SetNeedsLayout();
+				// Synchronous layout pass: materialize the new style's track subview
+				// hierarchy NOW, before the caller reaches into Subviews to set colors.
+				uiSwitch.LayoutIfNeeded();
+			}
+#endif
+		}
+
+		internal static bool IsReadyForColorReapply(this UISwitch uiSwitch)
+		{
+			var trackSubview = uiSwitch.GetTrackSubview();
+
+			return uiSwitch.Window is not null
+				&& trackSubview is not null
+				&& uiSwitch.Bounds.Width > 0
+				&& uiSwitch.Bounds.Height > 0
+				&& trackSubview.Bounds.Width > 0
+				&& trackSubview.Bounds.Height > 0;
+		}
+
+		static bool IsSlidingStyleRequiredForCustomColors()
+		{
+#if IOS || MACCATALYST
+			return OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26);
+#else
+			return false;
+#endif
 		}
 
 		internal static UIView? GetTrackSubview(this UISwitch uISwitch)

@MauiBot MauiBot added s/agent-fix-win AI found a better alternative fix than the PR and removed s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates labels May 25, 2026
@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented May 26, 2026

/review -b feature/refactor-copilot-yml -p ios

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against #2 automatically generated candidates and selected try-fix-2 as the strongest fix.

Why: try-fix-2 won because it is the only candidate with recorded passing targeted iOS Switch regression results and it preserves the native lifecycle observation required for iOS 26 color reapplication. It also improves the PR design by keeping MAUI virtual-view state and color application in SwitchProxy instead of inside the native UISwitch subclass.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (`try-fix-2`)
diff --git a/src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs b/src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs
index 20bad79c73..dcfaa11880 100644
--- a/src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs
+++ b/src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs
@@ -68,12 +68,17 @@ namespace Microsoft.Maui.Handlers
 
 			NSObject? _willEnterForegroundObserver;
 			NSObject? _windowDidBecomeKeyObserver;
+			bool _colorReapplyQueued;
 
 			public void Connect(ISwitch virtualView, UISwitch platformView)
 			{
 				_virtualView = new(virtualView);
 				_platformView = new(platformView);
-				(platformView as MauiSwitch)?.Connect(virtualView);
+				if (platformView is MauiSwitch mauiSwitch)
+				{
+					mauiSwitch.ColorReapplyRequested += OnColorReapplyRequested;
+					mauiSwitch.SetNeedsColorReapply();
+				}
 				platformView.ValueChanged += OnControlValueChanged;
 
 #if MACCATALYST
@@ -118,28 +123,65 @@ namespace Microsoft.Maui.Handlers
 					if (!platformView.On && VirtualView is ISwitch view && view.TrackColor is not null)
 					{
 						platformView.UpdateTrackColor(view);
-						(platformView as MauiSwitch)?.SetNeedsColorReapply();
+						RequestColorReapply(platformView);
 					}
 				});
 			}
 
 			void UpdateThumbAndTrackColor(UISwitch platformView)
 			{
+				RequestColorReapply(platformView);
+			}
+
+			void RequestColorReapply(UISwitch platformView)
+			{
+				if (platformView is MauiSwitch mauiSwitch)
+				{
+					mauiSwitch.SetNeedsColorReapply();
+				}
+				else
+				{
+					QueueColorReapply(platformView);
+				}
+			}
+
+			void OnColorReapplyRequested(object? sender, EventArgs e)
+			{
+				if (sender is UISwitch platformView)
+				{
+					QueueColorReapply(platformView);
+				}
+			}
+
+			void QueueColorReapply(UISwitch platformView)
+			{
+				if (_colorReapplyQueued)
+				{
+					return;
+				}
+
+				_colorReapplyQueued = true;
+
 				DispatchQueue.MainQueue.DispatchAsync(() =>
 				{
-					if (VirtualView is not ISwitch view || PlatformView is null)
+					_colorReapplyQueued = false;
+
+					if (VirtualView is not ISwitch view || PlatformView is null || !platformView.IsReadyForColorReapply())
 						return;
 
 					platformView.UpdateTrackColor(view);
 					platformView.UpdateThumbColor(view);
-					(platformView as MauiSwitch)?.SetNeedsColorReapply();
 				});
 			}
 
 			public void Disconnect(UISwitch platformView)
 			{
 				platformView.ValueChanged -= OnControlValueChanged;
-				(platformView as MauiSwitch)?.Disconnect();
+				if (platformView is MauiSwitch mauiSwitch)
+				{
+					mauiSwitch.ColorReapplyRequested -= OnColorReapplyRequested;
+					mauiSwitch.Disconnect();
+				}
 
 				if (_willEnterForegroundObserver is not null)
 				{
diff --git a/src/Core/src/Platform/iOS/MauiSwitch.cs b/src/Core/src/Platform/iOS/MauiSwitch.cs
index e56ce5bb7b..518254542e 100644
--- a/src/Core/src/Platform/iOS/MauiSwitch.cs
+++ b/src/Core/src/Platform/iOS/MauiSwitch.cs
@@ -7,30 +7,22 @@ namespace Microsoft.Maui.Platform
 {
 	internal class MauiSwitch : UISwitch
 	{
-		WeakReference<ISwitch>? _virtualView;
 		bool _colorReapplyQueued;
-		bool _isReapplyingColors;
-		bool _needsColorReapply;
 
-		public MauiSwitch(CGRect frame) : base(frame)
-		{
-		}
+		internal event EventHandler? ColorReapplyRequested;
 
-		public void Connect(ISwitch virtualView)
+		public MauiSwitch(CGRect frame) : base(frame)
 		{
-			_virtualView = new(virtualView);
-			SetNeedsColorReapply();
 		}
 
 		public void Disconnect()
 		{
-			_virtualView = null;
-			_needsColorReapply = false;
+			ColorReapplyRequested = null;
+			_colorReapplyQueued = false;
 		}
 
 		public void SetNeedsColorReapply()
 		{
-			_needsColorReapply = true;
 			SetNeedsLayout();
 			QueueColorReapply();
 		}
@@ -58,7 +50,7 @@ namespace Microsoft.Maui.Platform
 
 		void QueueColorReapply()
 		{
-			if (_colorReapplyQueued || !_needsColorReapply)
+			if (_colorReapplyQueued)
 			{
 				return;
 			}
@@ -68,34 +60,8 @@ namespace Microsoft.Maui.Platform
 			DispatchQueue.MainQueue.DispatchAsync(() =>
 			{
 				_colorReapplyQueued = false;
-				TryReapplyColors();
+				ColorReapplyRequested?.Invoke(this, EventArgs.Empty);
 			});
 		}
-
-		void TryReapplyColors()
-		{
-			if (_isReapplyingColors || !_needsColorReapply || !this.IsReadyForColorReapply())
-			{
-				return;
-			}
-
-			if (_virtualView is null || !_virtualView.TryGetTarget(out var virtualView))
-			{
-				return;
-			}
-
-			_isReapplyingColors = true;
-
-			try
-			{
-				this.ApplyTrackColor(virtualView);
-				this.ApplyThumbColor(virtualView);
-				_needsColorReapply = false;
-			}
-			finally
-			{
-				_isReapplyingColors = false;
-			}
-		}
 	}
 }

@AdamEssenmacher
Copy link
Copy Markdown
Author

I’m open to trying the try-fix-2 direction, but I’d like us to make that as a conscious design decision rather than treating it as a small cleanup.

I agree the architecture is appealing: MauiSwitch becomes the native lifecycle signal source, while SwitchProxy owns MAUI virtual-view state, readiness checks, and color application. That is a cleaner handler boundary.

The reason I haven’t applied it directly is that the current fix is more targeted and preserves the existing switch-specific state machine:

  • SwitchProxy already owns the handler-side weak refs and foreground/window notification paths.
  • MauiSwitch owns the native lifecycle reapply state for the iOS 26 UIKit reset timing: dirty flag, coalesced main-queue reapply, readiness check, and trait/layout/window re-arm.
  • SwitchExtensions.ReapplyColorsAfterStyleUpdate already routes style-change reapply through MauiSwitch.SetNeedsColorReapply() when the platform view is a MauiSwitch.

So the current PR is not introducing a completely separate competing pattern; it is extending the existing switch handler split.

The tradeoff is that try-fix-2 improves ownership boundaries, but also changes the production reapply state machine. In particular, the current implementation keeps an explicit dirty state until the native switch and track subview are ready. If we move that ownership into SwitchProxy, I’d want us to intentionally verify that we preserve the same retry semantics across initial layout, theme changes, foregrounding, and window reattach.

I’m happy to go that route if we agree the cleaner boundary is worth the broader production refactor in this PR. Otherwise, I think the current targeted fix plus the test hardening is the lower-risk path for the iOS 26 regression.

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented May 26, 2026

/review -b feature/refactor-copilot-yml -p ios

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 1 findings

See inline comments for details.


try
{
this.ApplyTrackColor(virtualView);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[moderate] Native Defaults PreservationMauiSwitch re-applies colors for every switch, including switches with both TrackColor and ThumbColor unset. On iOS 26 the default switch remains UISwitchStyle.Automatic, but this path still calls ApplyTrackColor, which sets the internal off-track subview to SecondarySystemFill for TrackColor == null. Concrete scenario: a default off switch is attached or gets a trait/window/layout reapply; the PR can overwrite UIKit's iOS 26 Automatic/Liquid Glass default even though no custom color was requested. Please skip lifecycle reapply when both colors are null, or avoid mutating the track in that case, and add a default-rendering or clear-both-colors check so Automatic defaults are preserved.

@MauiBot MauiBot added s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-fix-win AI found a better alternative fix than the PR labels May 26, 2026
@dotnet dotnet deleted a comment from MauiBot May 27, 2026
@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented May 27, 2026

/review -b feature/refactor-copilot-yml -p ios

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 1 findings

See inline comments for details.


try
{
this.ApplyTrackColor(virtualView);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[moderate] Native Defaults PreservationTryReapplyColors reapplies track/thumb colors even when the MAUI switch has neither TrackColor nor ThumbColor set. For an unstyled/off iOS 26 UISwitchStyle.Automatic switch, ApplyTrackColor writes SecondarySystemFill into the internal track subview after lifecycle resets such as MovedToWindow or trait changes, so UIKit's Automatic/Liquid Glass defaults can be overwritten even though the switch is uncustomized. Please only arm/reapply this lifecycle path when virtualView.TrackColor is not null || virtualView.ThumbColor is not null.

@MauiBot MauiBot added s/agent-fix-win AI found a better alternative fix than the PR and removed s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates labels May 27, 2026
@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented May 27, 2026

/review -b feature/refactor-copilot-yml -p ios

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 1 findings

See inline comments for details.

platformView.UpdateThumbColor(view);
}
}
platformView.UpdateTrackColor(view);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[moderate] Native Defaults PreservationUpdateThumbAndTrackColor runs on connect for every iOS/MacCatalyst 26 switch and unconditionally calls UpdateTrackColor(view). For an uncustomized off switch (TrackColor == null and ThumbColor == null), UpdateTrackColor keeps PreferredStyle as Automatic but then ApplyTrackColor writes SecondarySystemFill into the private track subview, so initial attach can overwrite UIKit's iOS 26 Automatic/Liquid Glass default even though no custom color was requested. Please skip this connect reapply unless a custom track or thumb color is set, or avoid mutating the track when both are null.

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented May 28, 2026

/review -b feature/refactor-copilot-yml

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 2 findings

See inline comments for details.


try
{
this.ApplyTrackColor(virtualView);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Native defaults preservationTryReapplyColors reapplies track/thumb colors even when both TrackColor and ThumbColor are null. For an uncustomized off switch on iOS/MacCatalyst 26, the style can remain Automatic, but ApplyTrackColor still writes SecondarySystemFill into the internal track subview and ApplyThumbColor writes null into ThumbTintColor, so layout/trait/window reapply paths can mutate UIKit's native Liquid Glass/default rendering. Please gate reapply on custom-color presence, or make the low-level apply methods no-op when the switch is Automatic with no custom colors, and add a regression test that verifies default/native rendering is preserved rather than only checking PreferredStyle.

[InlineData(true, true)]
public async Task CustomColorsUseSlidingStyleOniOS26(bool hasTrackColor, bool hasThumbColor)
{
if (!OperatingSystem.IsIOSVersionAtLeast(26))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[moderate] Regression prevention — The new iOS 26 tests only check OperatingSystem.IsIOSVersionAtLeast(26), but the production fix also changes MacCatalyst 26 behavior (IsMacCatalystVersionAtLeast(26) and the NSWindowDidBecomeKeyNotification reapply path). On MacCatalyst these tests skip, leaving the MacCatalyst-specific switch style/reapply behavior untested. Please use a shared iOS-or-MacCatalyst 26 guard or add MacCatalyst-specific coverage.

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented May 28, 2026

🤖 AI Summary

👋 @AdamEssenmacher — new AI review results are available. Please review the latest session below.

📊 Review Sessiond274312 · Harden iOS switch color tests · 2026-05-28 11:39 UTC
🚦 Gate — Test Before & After Fix

Gate Result: ❌ FAILED

Platform: ANDROID · Base: main · Merge base: b0ea772f

🩺 Fix does not pass the tests — every test still fails after applying the fix. The PR's change does not resolve the failure(s).

Test Without Fix (expect FAIL) With Fix (expect PASS)
📱 SwitchHandlerTests (CustomColorsUseSlidingStyleOniOS26, CustomColorsRenderOnInitialOffStateOniOS26, DefaultSwitchUsesAutomaticStyleOniOS26, ThumbColorClearsWhenResetOniOS26, CustomColorsReapplyAfterMovedToWindowOniOS26, CustomColorsUpdateAfterAppThemeChangeOniOS26) Category=Switch ✅ FAIL — 494s ❌ FAIL — 1018s
🔴 Without fix — 📱 SwitchHandlerTests (CustomColorsUseSlidingStyleOniOS26, CustomColorsRenderOnInitialOffStateOniOS26, DefaultSwitchUsesAutomaticStyleOniOS26, ThumbColorClearsWhenResetOniOS26, CustomColorsReapplyAfterMovedToWindowOniOS26, CustomColorsUpdateAfterAppThemeChangeOniOS26): FAIL ✅ · 494s

(truncated to last 15,000 chars)

8Ho/H4/F4PB6Px+PxeNoTudAn8P+c/P13F+wscnhBNJf57q80rOfFMJ8wzotgvCCWn8bCXmi9kfnEsRjRLBkviOWh8T5Kw6eaZ9v5XEZ+3TZ81yiGZRWHF8TSaSzgvAjywpAfAxWDXF777Mr9dwJcCO49sCG4V1IhOGYLIS+OplgML4hzZ64nXTWurwPpA9UHcgz4XeAWkCLoXhABceBGwVXBPAPub4A1wAi4EbCDdQHkBdFoORrXl3xRnsXTKAYFyGaQPaA+BvIBEIO6Cwq/BX290HcCLumA9WVYq2BlAEUHIRAbqFo4VYHDU/D+Kjg0CiN/DyMPQhSCvRR4sS6GvEiWTRReEGfPLDdAThBrQa8HdxsEt8LAari2CB8pw/UhXCmwSkEnUJb0P87VdyguLdyKhUkHJxJ4awp2TsGOEdj9NAw9Bsn7IIfBcLo45oszzvriPIsjCw5niWEt6MPg/gD4FdhwDdysYFsI1ytYDxTIFZKQlqICV/vEgqjaRrl6qACRhfdj2Gnh+TfhuYfgwF8Da+vCmEsUmUs5K7wgFsd8VkFthiCC+DroehDuKMDtCrYo6AOC3H9
      05-28 04:26:14.295  7872  7932 I DOTNET  :    Exception stack traces:    at Microsoft.Maui.DeviceTests.AssertionExtensions.<>c__DisplayClass32_0.<AssertContainsColor>b__0()
      05-28 04:26:14.295  7872  7932 I DOTNET  :    at System.Threading.Tasks.Task`1[[Android.Graphics.Bitmap, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065]].InnerInvoke()
      05-28 04:26:14.295  7872  7932 I DOTNET  :    at System.Threading.Tasks.Task.<>c.<.cctor>b__288_0(Object obj)
      05-28 04:26:14.295  7872  7932 I DOTNET  :    at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )
      05-28 04:26:14.295  7872  7932 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.295  7872  7932 I DOTNET  :    at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )
      05-28 04:26:14.295  7872  7932 I DOTNET  :    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& , Thread )
      05-28 04:26:14.295  7872  7932 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.295  7872  7932 I DOTNET  :    at Microsoft.Maui.DeviceTests.AssertionExtensions.AssertContainsColor(View view, Color expectedColor, IMauiContext mauiContext, Nullable`1 tolerance)
      05-28 04:26:14.295  7872  7932 I DOTNET  :    at Microsoft.Maui.DeviceTests.HandlerTestBasement.<>c__DisplayClass18_0.<<ValidateHasColor>b__0>d.MoveNext()
      05-28 04:26:14.295  7872  7932 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.295  7872  7932 I DOTNET  :    at Microsoft.Maui.Dispatching.DispatcherExtensions.<>c__DisplayClass3_0.<<DispatchAsync>b__0>d.MoveNext()
      05-28 04:26:14.295  7872  7932 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.295  7872  7932 I DOTNET  :    at Microsoft.Maui.Dispatching.DispatcherExtensions.<>c__DisplayClass2_0`1.<<DispatchAsync>b__0>d[[System.Boolean, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
      05-28 04:26:14.295  7872  7932 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.295  7872  7932 I DOTNET  :    at Microsoft.Maui.DeviceTests.SwitchHandlerTests.ThumbColorUpdatesCorrectly()
      05-28 04:26:14.295  7872  7932 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.295  7872  7932 I DOTNET  :    Execution time: 0.2217571
      05-28 04:26:14.295  7872  7932 I DOTNET  :    Test trait name: Category
      05-28 04:26:14.295  7872  7932 I DOTNET  :       value: Switch
      05-28 04:26:14.296  7872  7932 I DOTNET  : 	[FAIL] Thumb Color Updates Correctly
      05-28 04:26:14.296  7872  7932 I DOTNET  : 	[FAIL] Thumb Color Updates Correctly   Test name: Thumb Color Updates Correctly
      05-28 04:26:14.296  7872  7932 I DOTNET  :    Assembly:  [Microsoft.Maui.Core.DeviceTests, Version=10.0.0.0, Culture=neutral, PublicKeyToken=null]
      05-28 04:26:14.297  7872  7932 I DOTNET  :    Exception messages: Color Color [A=255, R=255, G=0, B=0] not found. This is what it looked like:<img>iVBORw0KGgoAAAANSUhEUgAAAIQAAACECAYAAABRRIOnAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAAA/rSURBVHic7Z17jB3Vfcc/v3Nm7mMf9u567QVjMDYuD2NMk7agKGkxhTTpU6iCVG1FGlo1UdWmUtWHQqMKXKlVioRKojRRWjVUrdSWh1pUgigtLyUNJBQwxtjYYDCY9WO99q69r7t3Zs45/WPu7J293vWuvXvte+n5SKM7unfuvM53fr/f+Z3HgMfj8Xg8Ho/H4/F4PB6Px+PxeDwej8fj8Xg8Ho/H4/F4PB6Px+PxeDwej8fj8Xg8Ho/H4/F4PB6Px+PxeNoTudAn8P+c/P13F+wscnhBNJf57q80rOfFMJ8wzotgvCCWn8bCXmi9kfnEsRjRLBkviOWh8T5Kw6eaZ9v5XEZ+3TZ81yiGZRWHF8TSaSzgvAjywpAfAxWDXF777Mr9dwJcCO49sCG4V1IhOGYLIS+OplgML4hzZ64nXTWurwPpA9UHcgz4XeAWkCLoXhABceBGwVXBPAPub4A1wAi4EbCDdQHkBdFoORrXl3xRnsXTKAYFyGaQPaA+BvIBEIO6Cwq/BX290HcCLumA9WVYq2BlAEUHIRAbqFo4VYHDU/D+Kjg0CiN/DyMPQhSCvRR4sS6GvEiWTRReEGfPLDdAThBrQa8HdxsEt8LAari2CB8pw/UhXCmwSkEnUJb0P87VdyguLdyKhUkHJxJ4awp2TsGOEdj9NAw9Bsn7IIfBcLo45oszzvriPIsjCw5niWEt6MPg/gD4FdhwDdysYFsI1ytYDxTIFZKQlqICV/vEgqjaRrl6qACRhfdj2Gnh+TfhuYfgwF8Da+vCmEsUmUs5K7wgFsd8VkFthiCC+DroehDuKMDtCrYo6AOC3H9
      05-28 04:26:14.297  7872  7932 I DOTNET  :    at System.Threading.Tasks.Task.<>c.<.cctor>b__288_0(Object obj)
      05-28 04:26:14.297  7872  7932 I DOTNET  :    at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )
      05-28 04:26:14.297  7872  7932 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.297  7872  7932 I DOTNET  :    at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )
      05-28 04:26:14.297  7872  7932 I DOTNET  :    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& , Thread )
      05-28 04:26:14.297  7872  7932 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.297  7872  7932 I DOTNET  :    at Microsoft.Maui.DeviceTests.AssertionExtensions.AssertContainsColor(View view, Color expectedColor, IMauiContext mauiContext, Nullable`1 tolerance)
      05-28 04:26:14.297  7872  7932 I DOTNET  :    at Microsoft.Maui.DeviceTests.HandlerTestBasement.<>c__DisplayClass18_0.<<ValidateHasColor>b__0>d.MoveNext()
      05-28 04:26:14.297  7872  7932 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.297  7872  7932 I DOTNET  :    at Microsoft.Maui.Dispatching.DispatcherExtensions.<>c__DisplayClass3_0.<<DispatchAsync>b__0>d.MoveNext()
      05-28 04:26:14.297  7872  7932 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.297  7872  7932 I DOTNET  :    at Microsoft.Maui.Dispatching.DispatcherExtensions.<>c__DisplayClass2_0`1.<<DispatchAsync>b__0>d[[System.Boolean, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
      05-28 04:26:14.297  7872  7932 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.297  7872  7932 I DOTNET  :    at Microsoft.Maui.DeviceTests.SwitchHandlerTests.ThumbColorUpdatesCorrectly()
      05-28 04:26:14.297  7872  7932 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.297  7872  7932 I DOTNET  :    Execution time: 0.2217571
      05-28 04:26:14.297  7872  7932 I DOTNET  :    Test trait name: Category
      05-28 04:26:14.297  7872  7932 I DOTNET  :       value: Switch
      05-28 04:26:14.297  7872  7932 I DOTNET  : 
      05-28 04:26:14.303  7872  7932 I DOTNET  : 	[PASS] RotationX Initialize Correctly
      05-28 04:26:14.310  7872  7932 I DOTNET  : 	[PASS] RotationX Initialize Correctly
      05-28 04:26:14.312  7872  7932 I DOTNET  : 	[PASS] Null Thumb Color Doesn't Crash
      05-28 04:26:14.316  7872  7932 I DOTNET  : 	[PASS] Setting Semantic Description makes element accessible
      05-28 04:26:14.320  7872  7932 I DOTNET  : 	[PASS] Is Toggled Initializes Correctly
      05-28 04:26:14.330  7872  7932 I DOTNET  : 	[PASS] FlowDirection is set correctly
      05-28 04:26:14.334  7872  7932 I DOTNET  : 	[PASS] FlowDirection is set correctly
      05-28 04:26:14.339  7872  7932 I DOTNET  : 	[PASS] Opacity is set correctly
      05-28 04:26:14.345  7872  7932 I DOTNET  : 	[PASS] Opacity is set correctly
      05-28 04:26:14.345  7872  7932 I DOTNET  : 	[IGNORED] Semantic Hint is set correctly
      05-28 04:26:14.349  7872  7932 I DOTNET  : 	[PASS] DisconnectHandlerDoesntCrash
      05-28 04:26:14.350  7872  7932 I DOTNET  : 	[PASS] HandlersHaveAllExpectedContructors
      05-28 04:26:14.353  7872  7932 I DOTNET  : 	[PASS] Rotation Initialize Correctly
      05-28 04:26:14.358  7872  7932 I DOTNET  : 	[PASS] Rotation Initialize Correctly
      05-28 04:26:14.361  7872  7932 I DOTNET  : 	[PASS] MinimumHeightInitializes
      05-28 04:26:14.362  7872  7932 I DOTNET  : 	[PASS] MinimumHeightInitializes
      05-28 04:26:14.410  7872  7932 I DOTNET  : 	[PASS] Visibility is set correctly
      05-28 04:26:14.411  7872  7932 I DOTNET  : 	[PASS] Visibility is set correctly
      05-28 04:26:14.413  7872  7932 I DOTNET  : 	[PASS] Updating Native Is On property updates Virtual View
      05-28 04:26:14.415  7872  7932 I DOTNET  : 	[PASS] Native View Bounds are not empty
      05-28 04:26:14.417  7872  7932 I DOTNET  : 	[PASS] Native View Bounds are not empty
      05-28 04:26:14.419  7872  7932 I DOTNET  : 	[PASS] ScaleY Initialize Correctly
      05-28 04:26:14.422  7872  7932 I DOTNET  : 	[PASS] ScaleY Initialize Correctly
      05-28 04:26:14.425  7872  7932 I DOTNET  : 	[PASS] Clip Initializes ContainerView Correctly
      05-28 04:26:14.430  7872  7932 I DOTNET  : 	[PASS] Automation Id is set correctly
      05-28 04:26:14.432  7872  7932 I DOTNET  : 	[PASS] Null Semantics Doesnt throw exception
      05-28 04:26:14.441  7872  7932 I DOTNET  : 	[PASS] Semantic Heading is set correctly
      05-28 04:26:14.445  7872  7932 I DOTNET  : 	[PASS] Setting Semantic Hint makes element accessible
      05-28 04:26:14.451  7872  7932 I DOTNET  : Microsoft.Maui.DeviceTests.SwitchHandlerTests 1.2676205 ms
      05-28 04:26:14.462  7872  7893 I DOTNET  : Failed tests:
      05-28 04:26:14.463  7872  7893 I DOTNET  : 1) 	[FAIL] Thumb Color Updates Correctly   Test name: Thumb Color Updates Correctly
      05-28 04:26:14.463  7872  7893 I DOTNET  :    Assembly:  [Microsoft.Maui.Core.DeviceTests, Version=10.0.0.0, Culture=neutral, PublicKeyToken=null]
      05-28 04:26:14.463  7872  7893 I DOTNET  :    Exception messages: Color Color [A=255, R=255, G=0, B=0] not found. This is what it looked like:<img>iVBORw0KGgoAAAANSUhEUgAAAIQAAACECAYAAABRRIOnAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAAA/rSURBVHic7Z17jB3Vfcc/v3Nm7mMf9u567QVjMDYuD2NMk7agKGkxhTTpU6iCVG1FGlo1UdWmUtWHQqMKXKlVioRKojRRWjVUrdSWh1pUgigtLyUNJBQwxtjYYDCY9WO99q69r7t3Zs45/WPu7J293vWuvXvte+n5SKM7unfuvM53fr/f+Z3HgMfj8Xg8Ho/H4/F4PB6Px+PxeDwej8fj8Xg8Ho/H4/F4PB6Px+PxeDwej8fj8Xg8Ho/H4/F4PB6Px+PxeNoTudAn8P+c/P13F+wscnhBNJf57q80rOfFMJ8wzotgvCCWn8bCXmi9kfnEsRjRLBkviOWh8T5Kw6eaZ9v5XEZ+3TZ81yiGZRWHF8TSaSzgvAjywpAfAxWDXF777Mr9dwJcCO49sCG4V1IhOGYLIS+OplgML4hzZ64nXTWurwPpA9UHcgz4XeAWkCLoXhABceBGwVXBPAPub4A1wAi4EbCDdQHkBdFoORrXl3xRnsXTKAYFyGaQPaA+BvIBEIO6Cwq/BX290HcCLumA9WVYq2BlAEUHIRAbqFo4VYHDU/D+Kjg0CiN/DyMPQhSCvRR4sS6GvEiWTRReEGfPLDdAThBrQa8HdxsEt8LAari2CB8pw/UhXCmwSkEnUJb0P87VdyguLdyKhUkHJxJ4awp2TsGOEdj9NAw9Bsn7IIfBcLo45oszzvriPIsjCw5niWEt6MPg/gD4FdhwDdysYFsI1ytYDxTIFZKQlqICV/vEgqjaRrl6qACRhfdj2Gnh+TfhuYfgwF8Da+vCmEsUmUs5K7wgFsd8VkFthiCC+DroehDuKMDtCrYo6AOC3H9
      05-28 04:26:14.463  7872  7893 I DOTNET  :    at System.Threading.Tasks.Task.<>c.<.cctor>b__288_0(Object obj)
      05-28 04:26:14.463  7872  7893 I DOTNET  :    at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )
      05-28 04:26:14.463  7872  7893 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.463  7872  7893 I DOTNET  :    at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread , ExecutionContext , ContextCallback , Object )
      05-28 04:26:14.463  7872  7893 I DOTNET  :    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& , Thread )
      05-28 04:26:14.463  7872  7893 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.463  7872  7893 I DOTNET  :    at Microsoft.Maui.DeviceTests.AssertionExtensions.AssertContainsColor(View view, Color expectedColor, IMauiContext mauiContext, Nullable`1 tolerance)
      05-28 04:26:14.463  7872  7893 I DOTNET  :    at Microsoft.Maui.DeviceTests.HandlerTestBasement.<>c__DisplayClass18_0.<<ValidateHasColor>b__0>d.MoveNext()
      05-28 04:26:14.463  7872  7893 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.463  7872  7893 I DOTNET  :    at Microsoft.Maui.Dispatching.DispatcherExtensions.<>c__DisplayClass3_0.<<DispatchAsync>b__0>d.MoveNext()
      05-28 04:26:14.463  7872  7893 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.463  7872  7893 I DOTNET  :    at Microsoft.Maui.Dispatching.DispatcherExtensions.<>c__DisplayClass2_0`1.<<DispatchAsync>b__0>d[[System.Boolean, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
      05-28 04:26:14.463  7872  7893 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.463  7872  7893 I DOTNET  :    at Microsoft.Maui.DeviceTests.SwitchHandlerTests.ThumbColorUpdatesCorrectly()
      05-28 04:26:14.463  7872  7893 I DOTNET  : --- End of stack trace from previous location ---
      05-28 04:26:14.463  7872  7893 I DOTNET  :    Execution time: 0.2217571
      05-28 04:26:14.463  7872  7893 I DOTNET  :    Test trait name: Category
      05-28 04:26:14.463  7872  7893 I DOTNET  :       value: Switch
      05-28 04:26:14.463  7872  7893 I DOTNET  : 
      05-28 04:26:14.493  7872  7893 I DOTNET  : Xml file was written to the provided writer.
      05-28 04:26:14.493  7872  7893 I DOTNET  : Tests run: 1323 Passed: 66 Inconclusive: 0 Failed: 1 Ignored: 1253
�[41m�[30mfail�[39m�[22m�[49m: Non-success instrumentation exit code: 1, expected: 0
�[40m�[32minfo�[39m�[22m�[49m: <<XHARNESS_RESULT_START>>
      {
        "version": 1,
        "machineName": "runnervmfuwn7",
        "exitCode": 1,
        "exitCodeName": "TESTS_FAILED",
        "platform": "android",
        "instrumentationExitCode": 1,
        "device": "emulator-5554",
        "deviceOsVersion": "API 30",
        "architecture": "x86_64",
        "files": [
          {
            "name": "testResults.xml",
            "type": "test-results"
          },
          {
            "name": "adb-logcat-com.microsoft.maui.core.devicetests-default.log",
            "type": "logcat"
          }
        ]
      }
      <<XHARNESS_RESULT_END>>
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.core.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.core.devicetests'
�[40m�[32minfo�[39m�[22m�[49m: Successfully uninstalled com.microsoft.maui.core.devicetests
XHarness exit code: 1 (TESTS_FAILED)
  Tests completed with exit code: 1

🟢 With fix — 📱 SwitchHandlerTests (CustomColorsUseSlidingStyleOniOS26, CustomColorsRenderOnInitialOffStateOniOS26, DefaultSwitchUsesAutomaticStyleOniOS26, ThumbColorClearsWhenResetOniOS26, CustomColorsReapplyAfterMovedToWindowOniOS26, CustomColorsUpdateAfterAppThemeChangeOniOS26): FAIL ❌ · 1018s

(truncated to last 15,000 chars)

n.Runtime.Android.dll -> Xamarin.AndroidX.Navigation.Runtime.Android.dll.so
  [116/129] System.Threading.Thread.dll -> System.Threading.Thread.dll.so
  [117/129] System.Text.Json.dll -> System.Text.Json.dll.so
  [50/129] Xamarin.AndroidX.Navigation.UI.dll -> Xamarin.AndroidX.Navigation.UI.dll.so
  [118/129] System.Threading.ThreadPool.dll -> System.Threading.ThreadPool.dll.so
  [119/129] System.Threading.dll -> System.Threading.dll.so
  [120/129] System.Xml.Linq.dll -> System.Xml.Linq.dll.so
  [51/129] Xamarin.AndroidX.RecyclerView.dll -> Xamarin.AndroidX.RecyclerView.dll.so
  [121/129] System.Xml.ReaderWriter.dll -> System.Xml.ReaderWriter.dll.so
  [122/129] System.Xml.XDocument.dll -> System.Xml.XDocument.dll.so
  [123/129] System.dll -> System.dll.so
  [52/129] Xamarin.AndroidX.SavedState.SavedState.Android.dll -> Xamarin.AndroidX.SavedState.SavedState.Android.dll.so
  [124/129] netstandard.dll -> netstandard.dll.so
  [125/129] Mono.Android.Export.dll -> Mono.Android.Export.dll.so
  [53/129] Xamarin.AndroidX.SwipeRefreshLayout.dll -> Xamarin.AndroidX.SwipeRefreshLayout.dll.so
  [126/129] Mono.Android.Runtime.dll -> Mono.Android.Runtime.dll.so
  [54/129] Xamarin.AndroidX.ViewPager.dll -> Xamarin.AndroidX.ViewPager.dll.so
  [127/129] Java.Interop.dll -> Java.Interop.dll.so
  [55/129] Xamarin.AndroidX.ViewPager2.dll -> Xamarin.AndroidX.ViewPager2.dll.so
  [56/129] Xamarin.Google.Android.Material.dll -> Xamarin.Google.Android.Material.dll.so
  [57/129] Xamarin.Kotlin.StdLib.dll -> Xamarin.Kotlin.StdLib.dll.so
  [58/129] Xamarin.KotlinX.Coroutines.Core.Jvm.dll -> Xamarin.KotlinX.Coroutines.Core.Jvm.dll.so
  [59/129] Xamarin.KotlinX.Serialization.Core.Jvm.dll -> Xamarin.KotlinX.Serialization.Core.Jvm.dll.so
  [60/129] xunit.abstractions.dll -> xunit.abstractions.dll.so
  [128/129] Mono.Android.dll -> Mono.Android.dll.so
  [61/129] xunit.assert.dll -> xunit.assert.dll.so
  [62/129] xunit.core.dll -> xunit.core.dll.so
  [63/129] xunit.execution.dotnet.dll -> xunit.execution.dotnet.dll.so
  [64/129] xunit.runner.utility.netcoreapp10.dll -> xunit.runner.utility.netcoreapp10.dll.so
  [65/129] System.Collections.Concurrent.dll -> System.Collections.Concurrent.dll.so
  [66/129] System.Collections.Immutable.dll -> System.Collections.Immutable.dll.so
  [67/129] System.Collections.NonGeneric.dll -> System.Collections.NonGeneric.dll.so
  [68/129] System.Collections.Specialized.dll -> System.Collections.Specialized.dll.so
  [69/129] System.Collections.dll -> System.Collections.dll.so
  [70/129] System.ComponentModel.Primitives.dll -> System.ComponentModel.Primitives.dll.so
  [71/129] System.ComponentModel.TypeConverter.dll -> System.ComponentModel.TypeConverter.dll.so
  [72/129] System.ComponentModel.dll -> System.ComponentModel.dll.so
  [73/129] System.Console.dll -> System.Console.dll.so
  [74/129] System.Diagnostics.Debug.dll -> System.Diagnostics.Debug.dll.so
  [75/129] System.Diagnostics.DiagnosticSource.dll -> System.Diagnostics.DiagnosticSource.dll.so
  [129/129] System.Private.CoreLib.dll -> System.Private.CoreLib.dll.so
  [76/129] System.Diagnostics.Process.dll -> System.Diagnostics.Process.dll.so
  [77/129] System.Diagnostics.Tools.dll -> System.Diagnostics.Tools.dll.so
  [78/129] System.Diagnostics.TraceSource.dll -> System.Diagnostics.TraceSource.dll.so
  [79/129] System.Diagnostics.Tracing.dll -> System.Diagnostics.Tracing.dll.so
  [80/129] System.Drawing.Primitives.dll -> System.Drawing.Primitives.dll.so
  [81/129] System.Drawing.dll -> System.Drawing.dll.so
  [82/129] System.Formats.Asn1.dll -> System.Formats.Asn1.dll.so
  [83/129] System.Globalization.dll -> System.Globalization.dll.so
  [84/129] System.IO.Compression.Brotli.dll -> System.IO.Compression.Brotli.dll.so
  [85/129] System.IO.Compression.dll -> System.IO.Compression.dll.so
  [86/129] System.IO.FileSystem.dll -> System.IO.FileSystem.dll.so
  [87/129] System.IO.Pipelines.dll -> System.IO.Pipelines.dll.so
  [88/129] System.IO.dll -> System.IO.dll.so
  [89/129] System.Linq.Expressions.dll -> System.Linq.Expressions.dll.so
  [90/129] System.Linq.dll -> System.Linq.dll.so
  [91/129] System.Memory.dll -> System.Memory.dll.so
  [92/129] System.Net.Http.dll -> System.Net.Http.dll.so
  [93/129] System.Net.NameResolution.dll -> System.Net.NameResolution.dll.so
  [94/129] System.Net.Primitives.dll -> System.Net.Primitives.dll.so
  [95/129] System.Net.Requests.dll -> System.Net.Requests.dll.so
  [96/129] System.Net.Sockets.dll -> System.Net.Sockets.dll.so
  [97/129] System.Numerics.Vectors.dll -> System.Numerics.Vectors.dll.so
  [98/129] System.ObjectModel.dll -> System.ObjectModel.dll.so
  [99/129] System.Private.Uri.dll -> System.Private.Uri.dll.so
  [100/129] System.Private.Xml.Linq.dll -> System.Private.Xml.Linq.dll.so
  [101/129] System.Private.Xml.dll -> System.Private.Xml.dll.so
  [102/129] System.Reflection.Extensions.dll -> System.Reflection.Extensions.dll.so
  [103/129] System.Reflection.TypeExtensions.dll -> System.Reflection.TypeExtensions.dll.so
  [104/129] System.Reflection.dll -> System.Reflection.dll.so
  [105/129] System.Runtime.Extensions.dll -> System.Runtime.Extensions.dll.so
  [106/129] System.Runtime.InteropServices.RuntimeInformation.dll -> System.Runtime.InteropServices.RuntimeInformation.dll.so
  [107/129] System.Runtime.InteropServices.dll -> System.Runtime.InteropServices.dll.so
  [108/129] System.Runtime.Loader.dll -> System.Runtime.Loader.dll.so
  [109/129] System.Runtime.Numerics.dll -> System.Runtime.Numerics.dll.so
  [110/129] System.Runtime.dll -> System.Runtime.dll.so
  [111/129] System.Security.Cryptography.dll -> System.Security.Cryptography.dll.so
  [112/129] System.Text.Encoding.dll -> System.Text.Encoding.dll.so
  [113/129] System.Text.Encodings.Web.dll -> System.Text.Encodings.Web.dll.so
  [114/129] System.Text.Json.dll -> System.Text.Json.dll.so
  [115/129] System.Text.RegularExpressions.dll -> System.Text.RegularExpressions.dll.so
  [116/129] System.Threading.Tasks.dll -> System.Threading.Tasks.dll.so
  [117/129] System.Threading.Thread.dll -> System.Threading.Thread.dll.so
  [118/129] System.Threading.ThreadPool.dll -> System.Threading.ThreadPool.dll.so
  [119/129] System.Threading.dll -> System.Threading.dll.so
  [120/129] System.Xml.Linq.dll -> System.Xml.Linq.dll.so
  [121/129] System.Xml.ReaderWriter.dll -> System.Xml.ReaderWriter.dll.so
  [122/129] System.Xml.XDocument.dll -> System.Xml.XDocument.dll.so
  [123/129] System.dll -> System.dll.so
  [124/129] netstandard.dll -> netstandard.dll.so
  [125/129] Java.Interop.dll -> Java.Interop.dll.so
  [126/129] Mono.Android.Export.dll -> Mono.Android.Export.dll.so
  [127/129] Mono.Android.Runtime.dll -> Mono.Android.Runtime.dll.so
  [128/129] Mono.Android.dll -> Mono.Android.dll.so
  [129/129] System.Private.CoreLib.dll -> System.Private.CoreLib.dll.so

Build succeeded.

/home/vsts/work/1/s/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt(23,1): warning RS0025: The symbol 'override Microsoft.Maui.Controls.TitleBar.OnBindingContextChanged() -> void' appears more than once in the public API files (https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/PublicApiAnalyzers/PublicApiAnalyzers.Help.md) [/home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj::TargetFramework=net10.0-android36.0]
    1 Warning(s)
    0 Error(s)

Time Elapsed 00:06:47.62
[11.0.0-prerelease.26230.4+92962e5c46ac08a66ded4c5696209cc60f1a232f] XHarness command issued: android test --app /home/vsts/work/1/s/artifacts/bin/Core.DeviceTests/Release/net10.0-android/com.microsoft.maui.core.devicetests-Signed.apk --package-name com.microsoft.maui.core.devicetests --device-id emulator-5554 -o artifacts/log --timeout 01:00:00 -v --arg TestFilter=Category=Switch
�[40m�[37mdbug�[39m�[22m�[49m: ADBRunner using ADB.exe supplied from /home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/tools/net10.0/any/../../../runtimes/any/native/adb/linux/adb
�[40m�[37mdbug�[39m�[22m�[49m: Full resolved path:'/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb'
�[40m�[32minfo�[39m�[22m�[49m: Will attempt to find device supporting architectures: 'arm64-v8a', 'x86_64'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb start-server'
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[32minfo�[39m�[22m�[49m: Finding attached devices/emulators...
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb devices -l'
�[40m�[37mdbug�[39m�[22m�[49m: Found 1 possible devices
�[40m�[37mdbug�[39m�[22m�[49m: Evaluating output line for device serial: emulator-5554          device product:sdk_gphone_x86_64 model:sdk_gphone_x86_64 device:generic_x86_64_arm64 transport_id:1
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 shell getprop ro.product.cpu.abilist'
�[40m�[37mdbug�[39m�[22m�[49m: Found 1 possible devices. Using 'emulator-5554'
�[40m�[32minfo�[39m�[22m�[49m: Active Android device set to serial 'emulator-5554'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 -s emulator-5554 shell getprop ro.product.cpu.abi'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 -s emulator-5554 shell getprop ro.build.version.sdk'
�[40m�[32minfo�[39m�[22m�[49m: Waiting for device to be available (max 5 minutes)
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 wait-for-device'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 -s emulator-5554 shell getprop sys.boot_completed'
�[40m�[37mdbug�[39m�[22m�[49m: sys.boot_completed = '1'
�[40m�[37mdbug�[39m�[22m�[49m: Waited 0 seconds for device boot completion
�[40m�[37mdbug�[39m�[22m�[49m: Working with emulator-5554 (API 30)
�[40m�[37mdbug�[39m�[22m�[49m: Check current adb install and/or package verification settings
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 shell settings get global verifier_verify_adb_installs'
�[40m�[37mdbug�[39m�[22m�[49m: verifier_verify_adb_installs = 0
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 shell settings get global package_verifier_enable'
�[40m�[37mdbug�[39m�[22m�[49m: package_verifier_enable = 
�[40m�[1m�[33mwarn�[39m�[22m�[49m: Installing debug apks on a device might be rejected with INSTALL_FAILED_VERIFICATION_FAILURE. Make sure to set 'package_verifier_enable' to '0'
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.core.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.core.devicetests'
�[41m�[30mfail�[39m�[22m�[49m: Waiting for command timed out: execution may be compromised
�[41m�[30mfail�[39m�[22m�[49m: Error: Exit code: -2
      Std out:
      
      
      
�[40m�[32minfo�[39m�[22m�[49m: Attempting to install /home/vsts/work/1/s/artifacts/bin/Core.DeviceTests/Release/net10.0-android/com.microsoft.maui.core.devicetests-Signed.apk
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 install /home/vsts/work/1/s/artifacts/bin/Core.DeviceTests/Release/net10.0-android/com.microsoft.maui.core.devicetests-Signed.apk'
�[41m�[30mfail�[39m�[22m�[49m: Waiting for command timed out: execution may be compromised
�[40m�[1m�[33mwarn�[39m�[22m�[49m: Installation failed; Will make one attempt to restart ADB server and the device, then retry the install
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 kill-server'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 start-server'
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 reboot'
�[41m�[1m�[37mcrit�[39m�[22m�[49m: Failed to reboot the device
      Exit code: 1
      Std out:
      
      
      Std err:
      error: device still authorizing
      
      
      
      Microsoft.DotNet.XHarness.Android.AdbFailureException: Failed to reboot the device
      Exit code: 1
      Std out:
      
      
      Std err:
      error: device still authorizing
      
      
      
         at Microsoft.DotNet.XHarness.Android.Execution.ProcessExecutionResults.ThrowIfFailed(String failureMessage) in /_/src/Microsoft.DotNet.XHarness.Android/Execution/IAdbProcessManager.cs:line 24
         at Microsoft.DotNet.XHarness.Android.AdbRunner.RebootAndroidDevice() in /_/src/Microsoft.DotNet.XHarness.Android/AdbRunner.cs:line 130
         at Microsoft.DotNet.XHarness.Android.AdbRunner.InstallApk(String apkPath) in /_/src/Microsoft.DotNet.XHarness.Android/AdbRunner.cs:line 690
         at Microsoft.DotNet.XHarness.CLI.Commands.Android.AndroidInstallCommand.InvokeHelper(ILogger logger, String apkPackageName, String appPackagePath, IReadOnlyCollection`1 requestedArchitectures, String deviceId, Nullable`1 apiVersion, TimeSpan bootTimeoutSeconds, AdbRunner runner, IDiagnosticsData diagnosticsData) in /_/src/Microsoft.DotNet.XHarness.CLI/Commands/Android/AndroidInstallCommand.cs:line 138
         at Microsoft.DotNet.XHarness.CLI.Commands.Android.AndroidTestCommand.InvokeCommand(ILogger logger) in /_/src/Microsoft.DotNet.XHarness.CLI/Commands/Android/AndroidTestCommand.cs:line 42
         at Microsoft.DotNet.XHarness.CLI.Android.AndroidCommand`1.InvokeInternal(ILogger logger) in /_/src/Microsoft.DotNet.XHarness.CLI/Commands/Android/AndroidCommand.cs:line 32
XHarness exit code: 91 (ADB_FAILURE)
  Tests completed with exit code: 91

⚠️ Failure Details

  • SwitchHandlerTests (CustomColorsUseSlidingStyleOniOS26, CustomColorsRenderOnInitialOffStateOniOS26, DefaultSwitchUsesAutomaticStyleOniOS26, ThumbColorClearsWhenResetOniOS26, CustomColorsReapplyAfterMovedToWindowOniOS26, CustomColorsUpdateAfterAppThemeChangeOniOS26) FAILED with fix (should pass)
📁 Fix files reverted (237 files)
  • eng/Signing.props
  • eng/pipelines/ci-copilot.yml
  • src/BlazorWebView/src/Maui/Android/BlazorAndroidWebView.cs
  • src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs
  • src/BlazorWebView/src/Maui/Android/WebKitWebViewClient.cs
  • src/Compatibility/Core/src/Android/Renderers/SwipeViewRenderer.cs
  • src/Compatibility/Core/src/MacOS/Extensions/NSMenuExtensions.cs
  • src/Compatibility/Core/src/iOS/EventTracker.cs
  • src/Compatibility/Core/src/iOS/Renderers/SwipeViewRenderer.cs
  • src/Controls/samples/Controls.Sample/Pages/Controls/MapsGalleries/MapPinsGallery.xaml
  • src/Controls/samples/Controls.Sample/Pages/Controls/MapsGalleries/MapPinsGallery.xaml.cs
  • src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
  • src/Controls/src/Core/ActionSheetArguments.cs
  • src/Controls/src/Core/AlertArguments.cs
  • src/Controls/src/Core/BindableObject.cs
  • src/Controls/src/Core/BindableProperty.cs
  • src/Controls/src/Core/Button/Button.iOS.cs
  • src/Controls/src/Core/Compatibility/Android/Resources/layout/flyoutcontent.axml
  • src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs
  • src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
  • src/Controls/src/Core/Compatibility/Handlers/Shell/Android/SearchHandlerAppearanceTracker.cs
  • src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutTemplatedContentRenderer.cs
  • src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFragmentContainer.cs
  • src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellItemRenderer.cs
  • src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellRenderer.cs
  • src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs
  • src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/SearchHandlerAppearanceTracker.cs
  • src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutLayoutManager.cs
  • src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellPageRendererTracker.cs
  • src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSectionRenderer.cs
  • src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSectionRootRenderer.cs
  • src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellTableViewController.cs
  • src/Controls/src/Core/Editor/Editor.Mapper.cs
  • src/Controls/src/Core/Editor/Editor.iOS.cs
  • src/Controls/src/Core/Element/Element.cs
  • src/Controls/src/Core/Entry/Entry.Mapper.cs
  • src/Controls/src/Core/Entry/Entry.iOS.cs
  • src/Controls/src/Core/FlyoutPage/FlyoutPage.cs
  • src/Controls/src/Core/FormattedString.cs
  • src/Controls/src/Core/GestureElement.cs
  • src/Controls/src/Core/Handlers/Items/Android/Adapters/GroupableItemsViewAdapter.cs
  • src/Controls/src/Core/Handlers/Items/Android/Adapters/ItemsViewAdapter.cs
  • src/Controls/src/Core/Handlers/Items/Android/GridLayoutSpanSizeLookup.cs
  • src/Controls/src/Core/Handlers/Items/Android/ItemContentView.cs
  • src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs
  • src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs
  • src/Controls/src/Core/Handlers/Items/Android/TemplatedItemViewHolder.cs
  • src/Controls/src/Core/Handlers/Items/CarouselViewHandler.Windows.cs
  • src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
  • src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs
  • src/Controls/src/Core/Handlers/Items2/iOS/CarouselViewController2.cs
  • src/Controls/src/Core/Handlers/Items2/iOS/GroupableItemsViewController2.cs
  • src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs
  • src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs
  • src/Controls/src/Core/Handlers/Items2/iOS/LayoutFactory2.cs
  • src/Controls/src/Core/Handlers/Items2/iOS/StructuredItemsViewController2.cs
  • src/Controls/src/Core/Handlers/Shapes/Polygon/PolygonHandler.Android.cs
  • src/Controls/src/Core/Handlers/Shapes/Polygon/PolygonHandler.Tizen.cs
  • src/Controls/src/Core/Handlers/Shapes/Polygon/PolygonHandler.Windows.cs
  • src/Controls/src/Core/Handlers/Shapes/Polygon/PolygonHandler.cs
  • src/Controls/src/Core/Handlers/Shapes/Polygon/PolygonHandler.iOS.cs
  • src/Controls/src/Core/Handlers/Shapes/Polyline/PolylineHandler.Android.cs
  • src/Controls/src/Core/Handlers/Shapes/Polyline/PolylineHandler.Tizen.cs
  • src/Controls/src/Core/Handlers/Shapes/Polyline/PolylineHandler.Windows.cs
  • src/Controls/src/Core/Handlers/Shapes/Polyline/PolylineHandler.cs
  • src/Controls/src/Core/Handlers/Shapes/Polyline/PolylineHandler.iOS.cs
  • src/Controls/src/Core/Handlers/Shell/ShellItemHandler.Windows.cs
  • src/Controls/src/Core/Handlers/Shell/Windows/ShellFlyoutItemView.cs
  • src/Controls/src/Core/Handlers/Shell/Windows/ShellView.cs
  • src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs
  • src/Controls/src/Core/ImageElement.cs
  • src/Controls/src/Core/Internals/WeakEventProxy.cs
  • src/Controls/src/Core/Items/SelectionList.cs
  • src/Controls/src/Core/Label/Label.Mapper.cs
  • src/Controls/src/Core/Label/Label.cs
  • src/Controls/src/Core/Label/Label.iOS.cs
  • src/Controls/src/Core/ListView/ListView.cs
  • src/Controls/src/Core/NavigationPage/NavigationPage.cs
  • src/Controls/src/Core/Page/Page.cs
  • src/Controls/src/Core/Platform/AlertManager/AlertManager.Android.cs
  • src/Controls/src/Core/Platform/AlertManager/AlertManager.cs
  • src/Controls/src/Core/Platform/Android/BottomNavigationViewUtils.cs
  • src/Controls/src/Core/Platform/Android/DragAndDropGestureHandler.cs
  • src/Controls/src/Core/Platform/Android/GenericAnimatorListener.cs
  • src/Controls/src/Core/Platform/Android/TabbedPageManager.cs
  • src/Controls/src/Core/Platform/ModalNavigationManager/ModalNavigationManager.iOS.cs
  • src/Controls/src/Core/Platform/Windows/CollectionView/ScrollHelpers.cs
  • src/Controls/src/Core/Platform/iOS/Extensions/FormattedStringExtensions.cs
  • src/Controls/src/Core/Platform/iOS/Extensions/LabelExtensions.cs
  • src/Controls/src/Core/PromptArguments.cs
  • src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt
  • src/Controls/src/Core/RadioButton/RadioButton.cs
  • src/Controls/src/Core/RadioButton/RadioButtonGroup.cs
  • src/Controls/src/Core/RadioButton/RadioButtonGroupController.cs
  • src/Controls/src/Core/ResourcesExtensions.cs
  • src/Controls/src/Core/Setter.cs
  • src/Controls/src/Core/Shadow.cs
  • src/Controls/src/Core/Shapes/Shape.cs
  • src/Controls/src/Core/Shell/Shell.cs
  • src/Controls/src/Core/Shell/ShellNavigationManager.cs
  • src/Controls/src/Core/SwipeView/SwipeItem.cs
  • src/Controls/src/Core/SwipeView/SwipeItemView.cs
  • src/Controls/src/Core/SwipeView/SwipeItems.cs
  • src/Controls/src/Core/SwipeView/SwipeView.cs
  • src/Controls/src/Core/TabbedPage/TabbedPage.Windows.cs
  • src/Controls/src/Core/VisualElement/VisualElement.cs
  • src/Controls/src/Core/VisualStateManager.cs
  • src/Controls/src/Core/WebView/WebViewSource.cs
  • src/Controls/src/Core/Window/Window.Android.cs
  • src/Controls/src/Core/Window/Window.cs
  • src/Controls/src/SourceGen/NodeSGExtensions.cs
  • src/Controls/src/Xaml/ApplyPropertiesVisitor.cs
  • src/Controls/src/Xaml/MarkupExtensions/OnIdiomExtension.cs
  • src/Controls/src/Xaml/MarkupExtensions/StaticResourceExtension.cs
  • src/Core/maps/src/Handlers/Map/MapHandler.Android.cs
  • src/Core/maps/src/Handlers/Map/MapHandler.iOS.cs
  • src/Core/maps/src/Handlers/MapPin/MapPinHandler.Android.cs
  • src/Core/maps/src/Platform/iOS/MauiMKMapView.cs
  • src/Core/maps/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
  • src/Core/src/Diagnostics/DiagnosticsManager.cs
  • src/Core/src/Diagnostics/IDiagnosticsManager.cs
  • src/Core/src/Diagnostics/Instrumentation/DiagnosticInstrumentation.cs
  • src/Core/src/Diagnostics/Instrumentation/LayoutArrangeInstrumentation.cs
  • src/Core/src/Diagnostics/Instrumentation/LayoutDiagnosticMetrics.cs
  • src/Core/src/Diagnostics/Instrumentation/LayoutMeasureInstrumentation.cs
  • src/Core/src/Graphics/MauiDrawable.Android.cs
  • src/Core/src/Handlers/Button/ButtonHandler.Android.cs
  • src/Core/src/Handlers/Button/ButtonHandler.cs
  • src/Core/src/Handlers/Button/ButtonHandler.iOS.cs
  • src/Core/src/Handlers/DatePicker/DatePickerHandler.Android.cs
  • src/Core/src/Handlers/DatePicker/DatePickerHandler.MacCatalyst.cs
  • src/Core/src/Handlers/Editor/EditorHandler.iOS.cs
  • src/Core/src/Handlers/Entry/EntryHandler.cs
  • src/Core/src/Handlers/Entry/EntryHandler.iOS.cs
  • src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs
  • src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Standard.cs
  • src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Tizen.cs
  • src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs
  • src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs
  • src/Core/src/Handlers/Image/ImageHandler.Windows.cs
  • src/Core/src/Handlers/Image/ImageHandler.iOS.cs
  • src/Core/src/Handlers/Label/LabelHandler.cs
  • src/Core/src/Handlers/Label/LabelHandler.iOS.cs
  • src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
  • src/Core/src/Handlers/ProgressBar/ProgressBarHandler.iOS.cs
  • src/Core/src/Handlers/RadioButton/RadioButtonHandler.cs
  • src/Core/src/Handlers/RadioButton/RadioButtonHandler.iOS.cs
  • src/Core/src/Handlers/RefreshView/RefreshViewHandler.Windows.cs
  • src/Core/src/Handlers/ScrollView/ScrollViewHandler.Android.cs
  • src/Core/src/Handlers/ScrollView/ScrollViewHandler.Windows.cs
  • src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs
  • src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs
  • src/Core/src/Handlers/SearchBar/SearchBarHandler2.Android.cs
  • src/Core/src/Handlers/ShapeView/ShapeViewHandler.Standard.cs
  • src/Core/src/Handlers/ShapeView/ShapeViewHandler.cs
  • src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs
  • src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.Android.cs
  • src/Core/src/Handlers/SwipeItemMenuItem/SwipeItemMenuItemHandler.iOS.cs
  • src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs
  • src/Core/src/Handlers/TimePicker/TimePickerHandler.Android.cs
  • src/Core/src/Handlers/TimePicker/TimePickerHandler.Windows.cs
  • src/Core/src/Handlers/TimePicker/TimePickerHandler.cs
  • src/Core/src/Handlers/TimePicker/TimePickerHandler.iOS.cs
  • src/Core/src/Handlers/WebView/WebViewHandler.Android.cs
  • src/Core/src/Hosting/EssentialsMauiAppBuilderExtensions.cs
  • src/Core/src/Hosting/LifecycleEvents/AppHostBuilderExtensions.Android.cs
  • src/Core/src/Platform/Android/ActivityIndicatorExtensions.cs
  • src/Core/src/Platform/Android/ActivityResultCallbackRegistry.cs
  • src/Core/src/Platform/Android/BorderDrawable.cs
  • src/Core/src/Platform/Android/ContainerView.cs
  • src/Core/src/Platform/Android/EditTextExtensions.cs
  • src/Core/src/Platform/Android/MauiAppCompatActivity.Lifecycle.cs
  • src/Core/src/Platform/Android/MauiAppCompatActivity.cs
  • src/Core/src/Platform/Android/MauiHybridWebView.cs
  • src/Core/src/Platform/Android/MauiHybridWebViewClient.cs
  • src/Core/src/Platform/Android/MauiScrollView.cs
  • src/Core/src/Platform/Android/MauiSearchView.cs
  • src/Core/src/Platform/Android/MauiSwipeRefreshLayout.cs
  • src/Core/src/Platform/Android/MauiSwipeView.cs
  • src/Core/src/Platform/Android/MauiWebView.cs
  • src/Core/src/Platform/Android/MauiWebViewClient.cs
  • src/Core/src/Platform/Android/Navigation/NavigationRootManager.cs
  • src/Core/src/Platform/Android/RadioButtonExtensions.cs
  • src/Core/src/Platform/Android/SafeAreaExtensions.cs
  • src/Core/src/Platform/Android/SearchViewExtensions.cs
  • src/Core/src/Platform/Android/TimePickerExtensions.cs
  • src/Core/src/Platform/Windows/ContentPanel.cs
  • src/Core/src/Platform/Windows/LayoutPanel.cs
  • src/Core/src/Platform/Windows/MauiPasswordTextBox.cs
  • src/Core/src/Platform/Windows/MauiToolbar.xaml.cs
  • src/Core/src/Platform/Windows/RadioButtonExtensions.cs
  • src/Core/src/Platform/Windows/RootNavigationView.cs
  • src/Core/src/Platform/Windows/ScrollViewerExtensions.cs
  • src/Core/src/Platform/Windows/TimePickerExtensions.cs
  • src/Core/src/Platform/Windows/WebViewExtensions.cs
  • src/Core/src/Platform/iOS/ButtonExtensions.cs
  • src/Core/src/Platform/iOS/KeyboardAcceleratorExtensions.cs
  • src/Core/src/Platform/iOS/LayerExtensions.cs
  • src/Core/src/Platform/iOS/MauiPageControl.cs
  • src/Core/src/Platform/iOS/MauiScrollView.cs
  • src/Core/src/Platform/iOS/MauiSwipeView.cs
  • src/Core/src/Platform/iOS/MauiTextView.cs
  • src/Core/src/Platform/iOS/MauiView.cs
  • src/Core/src/Platform/iOS/PickerExtensions.cs
  • src/Core/src/Platform/iOS/SwitchExtensions.cs
  • src/Core/src/Platform/iOS/TabbedViewExtensions.cs
  • src/Core/src/Platform/iOS/TextFieldExtensions.cs
  • src/Core/src/Platform/iOS/TimePickerExtensions.cs
  • src/Core/src/Platform/iOS/WrapperView.cs
  • src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
  • src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
  • src/Core/src/ViewExtensions.cs
  • src/Core/src/WindowExtensions.cs
  • src/Essentials/src/AssemblyInfo/AssemblyInfo.shared.cs
  • src/Essentials/src/Clipboard/Clipboard.shared.cs
  • src/Essentials/src/FilePicker/FilePicker.tizen.cs
  • src/Essentials/src/FileSystem/FileSystemUtils.android.cs
  • src/Essentials/src/FileSystem/FileSystemUtils.shared.cs
  • src/Essentials/src/MainThread/MainThread.netstandard.cs
  • src/Essentials/src/MainThread/MainThread.shared.cs
  • src/Essentials/src/MediaPicker/MediaPicker.ios.cs
  • src/Essentials/src/MediaPicker/MediaPicker.tizen.cs
  • src/Essentials/src/Types/Shared/WebUtils.shared.cs
  • src/Graphics/src/Graphics/Platforms/Android/PlatformGraphicsView.cs
  • src/Graphics/src/Graphics/Platforms/MaciOS/PlatformCanvas.cs
  • src/Graphics/src/Graphics/Platforms/Windows/PlatformGraphicsView.cs
  • src/Graphics/src/Graphics/Platforms/iOS/PlatformGraphicsView.cs
  • src/SingleProject/Resizetizer/src/GenerateTizenManifest.cs
  • src/SingleProject/Resizetizer/src/SkiaSharpSvgTools.cs

New files (not reverted):

  • src/Controls/src/Core/Handlers/Items/iOS/IScrollTrackingDelegator.cs
  • src/Controls/src/Core/Platform/AlertManager/DelegateAlertSubscription.cs
  • src/Core/src/Handlers/HybridWebView/HybridWebViewHelper.cs
  • src/Core/src/Platform/Android/AppbarLayoutExtensions.cs
  • src/Core/src/Platform/Android/IBackNavigationState.cs
  • src/Core/src/Platform/Android/RefreshViewWebViewScrollCapture.cs
  • src/Core/src/Platform/Windows/MauiLayoutAutomationPeer.cs
  • src/Core/src/Platform/iOS/MauiProgressView.cs
  • src/Core/src/Platform/iOS/MauiSwitch.cs
  • src/Core/src/ScreenshotDispatch.cs

🧪 UI Tests — ViewBaseTests

Detected UI test categories: ViewBaseTests


🔍 Regression Cross-Reference

🔍 Regression Cross-Reference

🟢 No regression risks detected. No labeled bug-fix PRs in the last 6 months touched the modified files.


🔍 Pre-Flight — Context & Validation

Issue: #35257 - iOS 26 Switch default color for Off and On is incorrect + Off Color is not applied at start + Thumb Colors is not applied
PR: #35385 - [iOS] Fix Switch custom colors on iOS 26
Platforms Affected: iOS, MacCatalyst
Files Changed: 3 implementation, 2 test

Key Findings

  • The PR changes iOS/MacCatalyst Switch handling to use UISwitchStyle.Sliding for custom-colored switches on iOS/MacCatalyst 26+, reapply colors after lifecycle/style changes, clear thumb tint when reset, and add focused handler/UI coverage.
  • The linked issue reports iOS 26+ Switch custom OffColor/TrackColor and ThumbColor rendering regressions; validation comments confirm reproducibility on iOS.
  • Existing unresolved review feedback focuses on preserving native Automatic/Liquid Glass defaults for unstyled switches; reapply paths can still call color application when both TrackColor and ThumbColor are null.
  • Gate was already completed separately and failed; per instruction it was not re-run and gate/content.md was not modified.

Code Review Summary

Verdict: NEEDS_DISCUSSION
Confidence: medium
Errors: 0 | Warnings: 1 | Suggestions: 0

Key code review findings:

  • ⚠️ src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs:133 and src/Core/src/Platform/iOS/MauiSwitch.cs:91 reapply track/thumb colors without first checking whether TrackColor or ThumbColor is set. For an uncustomized off switch on iOS/MacCatalyst 26, UpdatePreferredStyle keeps UISwitchStyle.Automatic, but ApplyTrackColor can write fallback SecondarySystemFill into the internal track subview, conflicting with native default preservation.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #35385 Use MauiSwitch, UISwitchStyle.Sliding for custom colors, lifecycle reapply, thumb reset clearing, and tests ❌ FAILED (Gate, pre-run) SwitchHandler.iOS.cs, MauiSwitch.cs, SwitchExtensions.cs, tests Original PR; gate output left intact

🔬 Code Review — Deep Analysis

Code Review — PR #35385

Independent Assessment

What this changes: The PR changes the iOS/MacCatalyst Switch handler to create a MauiSwitch subclass, switch UISwitch to Sliding style on iOS/MacCatalyst 26+ when MAUI track/thumb colors are set, and reapply track/thumb colors after connect, layout, window attach, and trait changes. It also adds focused iOS device tests and enables HostApp startup test selection on iOS.

Inferred motivation: iOS 26 appears to reset or ignore custom Switch colors under the default/automatic switch style, so the change opts customized switches into the classic style and retries color application after UIKit lifecycle/style resets.

Reconciliation with PR Narrative

Author claims: The PR fixes #35257 by using UISwitchStyle.Sliding only for switches with non-null MAUI colors, leaves unstyled switches as Automatic to preserve native iOS 26 appearance, reapplies colors after lifecycle resets, clears thumb tint when reset, and adds handler/Appium visual coverage.

Agreement/disagreement: The code matches the custom-color/style and reapply goals, and the added tests cover initial render, reset, reattach, and theme-change paths. The remaining concern is with the native-default preservation claim: new reapply paths still call UpdateTrackColor/ApplyTrackColor for uncustomized switches, which can mutate the private off-track subview even while PreferredStyle remains Automatic.

Findings

⚠️ Warning — Unstyled iOS 26 switches can still have native Automatic defaults overwritten

src/Core/src/Handlers/Switch/SwitchHandler.iOS.cs:133 and src/Core/src/Platform/iOS/MauiSwitch.cs:91 reapply track/thumb colors without first checking whether TrackColor or ThumbColor is set. For an uncustomized off switch on iOS/MacCatalyst 26, UpdatePreferredStyle keeps UISwitchStyle.Automatic, but ApplyTrackColor then writes the fallback SecondarySystemFill into the internal track subview. That conflicts with the stated goal of preserving the native Automatic/Liquid Glass default for unstyled switches. This is already present as unresolved PR feedback, so I would not add a duplicate inline comment, but it should be addressed or explicitly accepted before merge.

Devil's Advocate

The fallback off-track assignment existed before this PR in the normal TrackColor mapper path, so the new code may not be the first place that can touch an unstyled switch. However, this PR adds explicit connect/lifecycle reapply paths and claims unstyled iOS 26 switches preserve native defaults; those added paths make the default-rendering risk more likely and directly relevant. The expert reviewer returned no new inline findings beyond existing unresolved comments. CI could not be checked through gh pr checks because the local CLI is unauthenticated; GitHub check data available via API shows overall status pending with Build Analysis still in progress.

Verdict: NEEDS_DISCUSSION

Confidence: medium
Summary: The custom-color approach is generally sound and the tests are targeted, but there is an unresolved native-default preservation concern on newly added reapply paths. I would not call this LGTM until that concern is resolved or a maintainer explicitly accepts the behavior, and pending CI also prevents an LGTM verdict.


🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix-1 / claude-opus-4.6 Gate lifecycle color reapply/application on custom color existence (HasCustomColors) ⚠️ Blocked 3 files Android cannot validate iOS 26 UISwitch behavior; self-review clean. Full diff: try-fix/attempt-1/fix.diff
2 try-fix-2 / claude-opus-4.7 Preserve native defaults in low-level color application by skipping writes when iOS/MacCatalyst 26+ switch remains Automatic ⚠️ Blocked 3 files Simplest candidate; Android run cannot validate iOS-only path; self-review clean. Full diff: try-fix/attempt-2/fix.diff
3 try-fix-3 / gpt-5.3-codex Capture and restore native UIKit switch defaults when MAUI custom colors are absent ⚠️ Blocked 3 files More complex stateful default preservation; Android cannot validate iOS-only path; self-review clean. Full diff: try-fix/attempt-3/fix.diff
4 try-fix-4 / gpt-5.5 Drive custom-color refresh through handler mapper UpdateValue ordering rather than direct lifecycle apply ⚠️ Blocked 3 files Preserves mapper extensibility but still blocked on Android-only validation; self-review clean. Full diff: try-fix-4/fix.diff
PR PR #35385 Current PR fix: MauiSwitch, style switching, lifecycle reapply, thumb reset clearing, tests ❌ FAILED (Gate) 5 files Gate failed before this phase and was not re-run

Candidate Details

# Failure Analysis / Learning
1 Broad custom-color gates address default preservation but are invasive across lifecycle and helper code. Android test execution cannot exercise the iOS/MacCatalyst .iOS.cs path.
2 Moving default preservation into ApplyTrackColor/ApplyThumbColor is the smallest alternative and centralizes protection at the setter layer. Android validation remains non-demonstrative for iOS 26.
3 Native default capture/restore is conceptually robust for reset scenarios, but adds state and lifecycle complexity that is harder to justify without iOS 26 validation.
4 Mapper-driven refresh aligns with handler extensibility (UpdateValue) but is more complex than candidate 2 and still needs iOS 26 execution to prove lifecycle behavior.

Cross-Pollination

Model Round New Ideas? Details
claude-opus-4.6 1 Yes Candidate 1: lifecycle/custom-color gate.
claude-opus-4.7 1 Yes Candidate 2: low-level Automatic/native-default preservation.
gpt-5.3-codex 1 Yes Candidate 3: native default capture/restore.
gpt-5.5 1 Yes Candidate 4: handler/mapper refresh orchestration.
all 2 No passing candidate All meaningfully different approaches were blocked by the caller-specified Android platform, which cannot validate the iOS 26 UIKit behavior.

Exhausted: Yes — four distinct approaches were generated and tested as far as the Android platform allows; none can be marked pass because the target bug/fix path is iOS/MacCatalyst-specific.
Selected Fix: None — no candidate passed all tests or was demonstrably better under the required Android validation. Candidate #2 is the most promising alternative on design grounds because it is the smallest and centralizes native-default preservation, but it still requires iOS/MacCatalyst 26+ validation before it can be preferred over the PR.


📋 Report — Final Recommendation

Comparative Report — PR #35385

Candidate ranking

Rank Candidate Result status Assessment
1 pr-plus-reviewer Not rerun; Android cannot validate iOS/MacCatalyst 26 behavior Best candidate. It keeps the PR's lifecycle/style fix and applies the expert reviewer's minimal native-default preservation guard plus MacCatalyst test coverage correction.
2 try-fix-2 Blocked Strongest independent try-fix. It centralizes preservation in ApplyTrackColor/ApplyThumbColor, which is the smallest robust way to prevent Automatic/Liquid Glass defaults from being overwritten. It does not include the expert reviewer's MacCatalyst test guard correction.
3 try-fix-1 Blocked Correctly gates lifecycle reapply on custom-color presence, but spreads the guard across more call sites and is more invasive than setter-layer preservation.
4 try-fix-4 Blocked Mapper-driven refresh respects handler extensibility, but adds orchestration complexity beyond what is needed to fix the reviewer-identified default-preservation bug.
5 try-fix-3 Blocked Capturing/restoring native defaults is conceptually robust, but adds state and lifecycle complexity that is harder to justify without iOS/MacCatalyst 26 validation.
6 pr Gate failed The submitted PR fixes custom-color rendering paths but still has the expert-confirmed native-default mutation issue for uncustomized iOS/MacCatalyst 26 switches. Candidates with failed regression/gate behavior rank below candidates that have not failed and address the identified issue.

Decision

Winner: pr-plus-reviewer.

The raw PR has an actionable correctness/regression issue: it can preserve PreferredStyle.Automatic while still writing fallback/null tint values into UIKit internals during reapply. pr-plus-reviewer keeps the useful parts of the PR and applies the smallest targeted correction at the low-level color application layer, so all current and future reapply callers preserve native defaults when no custom color is set. It also improves test coverage so MacCatalyst 26 does not silently skip the same behavior.

Notes on test status

The gate result supplied to this phase was failed and was not rerun. All STEP 5a try-fix candidates were marked blocked because the requested Android platform cannot validate iOS/MacCatalyst 26 UISwitch behavior; their observed Android failures were not tied to the iOS-only changes. Because no candidate has a clean platform-appropriate pass, the ranking is based on correctness, regression risk, and simplicity, while still ranking the raw PR below candidates that address the expert-confirmed issue.


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community ✨ Community Contribution s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iOS 26 Switch default color for Off and On is incorrect + Off Color is not applied at start + Thumb Colors is not applied

5 participants