Repository navigation
[release/10.0.1xx-sr10] Preserve custom iOS button styling #37726
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
kubaflo
merged 9 commits into
dotnet:vs/10.0.101
from
kubaflo:release-agent/sr10-backport-36769
Aug 22, 2026
Merged
Changes from 3 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
b453ea4
[release/10.0.1xx-sr10] Preserve custom iOS button styling
devanathan-vaithiyanathan 2b0d9dc
Fail UI repro when native color is unavailable
6c898fa
[release/10.0.1xx-sr10] Track initial iOS button background mapping
34bc347
Preserve native button style across handler reuse
e349dcb
Clarify button background regression comments
48ec520
Avoid state allocation for empty Button backgrounds
9557dde
Run destructive Button verification once
8ef8ece
Track only applied iOS Button backgrounds
c28674d
Preserve native button style for unsupported gradients
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
108 changes: 108 additions & 0 deletions
108
src/Controls/tests/TestCases.HostApp/Issues/Issue36749.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| #if IOS || MACCATALYST | ||
| using Microsoft.Maui.Handlers; | ||
| using UIKit; | ||
| #endif | ||
|
|
||
| namespace Maui.Controls.Sample.Issues; | ||
|
|
||
| // Regression: ButtonHandler.iOS.cs (introduced in 10.0.80 via PR #33346) unconditionally reset | ||
| // BackgroundColor to UIColor.Clear when the cross-platform Background was null. This wiped native | ||
| // colors set by a custom UIButton subclass returned from CreatePlatformView(). | ||
| [Issue(IssueTracker.Github, 36749, | ||
| "ButtonHandler clears native platform-view styling set by custom handlers when Background/TextColor are null", | ||
| PlatformAffected.iOS | PlatformAffected.macOS)] | ||
| public class Issue36749 : ContentPage | ||
| { | ||
| public const string ResultLabelId = "Issue36749Result"; | ||
|
|
||
| readonly Label _resultLabel; | ||
| readonly Button _button; | ||
|
|
||
| public Issue36749() | ||
| { | ||
| _resultLabel = new Label | ||
| { | ||
| AutomationId = ResultLabelId, | ||
| Text = "Checking...", | ||
| HorizontalTextAlignment = TextAlignment.Center | ||
| }; | ||
|
|
||
| // Issue36749Button routes to Issue36749ButtonHandler (registered in MauiProgram.cs). | ||
| // The handler's CreatePlatformView() returns a UIButton subclass that self-styles with | ||
| // BackgroundColor = UIColor.Cyan. No cross-platform Background is set on this button. | ||
| _button = new Issue36749Button | ||
| { | ||
| Text = "Test Button", | ||
| AutomationId = "Issue36749Button" | ||
| }; | ||
|
|
||
| Content = new VerticalStackLayout | ||
| { | ||
| Padding = 20, | ||
| Spacing = 20, | ||
| Children = { _button, _resultLabel } | ||
| }; | ||
| } | ||
|
|
||
| protected override void OnAppearing() | ||
| { | ||
| base.OnAppearing(); | ||
|
|
||
| #if IOS || MACCATALYST | ||
| if (_button.Handler?.PlatformView is not UIButton platformButton || | ||
| platformButton.BackgroundColor is not UIColor backgroundColor) | ||
| { | ||
| // Handler, platform view, or background unavailable — mark as FAIL so the test fails | ||
| // immediately rather than timing out on "Checking...". | ||
| _resultLabel.Text = "FAIL"; | ||
| return; | ||
| } | ||
|
|
||
| // The native UIButton subclass sets BackgroundColor = UIColor.Cyan in its constructor. | ||
| // With the regression: initial null-Background mapping resets it to UIColor.Clear. | ||
| // With the fix: the Window-null check skips the reset, preserving Cyan. | ||
| backgroundColor.GetRGBA(out var r, out var g, out var b, out var a); | ||
|
|
||
| // UIColor.Cyan = R:0, G:1, B:1, A:1 | ||
| bool preservedInitialColor = r < 0.01 && g > 0.99 && b > 0.99 && a > 0.99; | ||
|
|
||
| // A later null mapping must clear MAUI-applied state even while the native view is | ||
| // detached. Window-based initial-map detection incorrectly leaves this color red. | ||
| _button.Background = new SolidColorBrush(Colors.Red); | ||
| platformButton.RemoveFromSuperview(); | ||
| _button.Background = null; | ||
|
|
||
| bool clearedDetachedColor = false; | ||
| if (platformButton.BackgroundColor is UIColor detachedColor) | ||
| { | ||
| detachedColor.GetRGBA(out _, out _, out _, out var detachedAlpha); | ||
| clearedDetachedColor = detachedAlpha < 0.01; | ||
| } | ||
|
|
||
| _resultLabel.Text = preservedInitialColor && clearedDetachedColor ? "PASS" : "FAIL"; | ||
| #endif | ||
| } | ||
| } | ||
|
|
||
| // Custom Button subtype used purely to route to Issue36749ButtonHandler. | ||
| public class Issue36749Button : Button { } | ||
|
|
||
| #if IOS || MACCATALYST | ||
|
|
||
| // Simulates a design-system UIButton subclass that self-styles in its constructor. | ||
| // The cross-platform Button intentionally leaves Background and TextColor unset. | ||
| class Issue36749NativeStyledButton : UIButton | ||
| { | ||
| public Issue36749NativeStyledButton() : base(UIButtonType.System) | ||
| { | ||
| BackgroundColor = UIColor.Cyan; | ||
| SetTitleColor(UIColor.DarkGray, UIControlState.Normal); | ||
| } | ||
| } | ||
|
|
||
| class Issue36749ButtonHandler : ButtonHandler | ||
| { | ||
| protected override UIButton CreatePlatformView() => new Issue36749NativeStyledButton(); | ||
| } | ||
|
|
||
| #endif | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue36749.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| #if IOS || MACCATALYST //This is an iOS-specific issue and can be reproduced by extending UIButton. Therefore, the test was added only for iOS and Mac Catalyst. | ||
| using NUnit.Framework; | ||
| using UITest.Appium; | ||
| using UITest.Core; | ||
|
|
||
| namespace Microsoft.Maui.TestCases.Tests.Issues; | ||
|
|
||
| public class Issue36749 : _IssuesUITest | ||
| { | ||
| public Issue36749(TestDevice device) : base(device) { } | ||
|
|
||
| public override string Issue => "ButtonHandler clears native platform-view styling set by custom handlers when Background/TextColor are null"; | ||
|
|
||
| [Test] | ||
| [Category(UITestCategories.Button)] | ||
| public void NativeStyledButtonPreservesInitialAndClearsDetachedBackground() | ||
| { | ||
| App.WaitForElement("Issue36749Result"); | ||
|
|
||
| var resultText = App.FindElement("Issue36749Result").GetText(); | ||
|
|
||
| Assert.That(resultText, Is.EqualTo("PASS"), | ||
| "Native BackgroundColor set in a UIButton subclass constructor should be preserved " + | ||
| "during the initial mapping, while later null Background updates must clear " + | ||
| "MAUI-applied state even when the native button is detached. " + | ||
| "Regression: PR #33346 unconditionally reset BackgroundColor to UIColor.Clear " + | ||
| "on the initial null-paint mapping, wiping native styling."); | ||
| } | ||
| } | ||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.