Skip to content
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue34422.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 34422, "SearchBar clear button still appears on MacCatalyst after clearing input", PlatformAffected.macOS)]
public class Issue34422 : ContentPage
{
readonly SearchBar _searchBar;

public Issue34422()
{
_searchBar = new SearchBar
{
Placeholder = "Search...",
AutomationId = "TestSearchBar"
};

var addTextButton = new Button
{
Text = "Add Text",
AutomationId = "AddTextButton"
};

addTextButton.Clicked += (s, e) =>
{
_searchBar.Text = "Search text";
};

var clearButton = new Button
{
Text = "Clear SearchBar Text",
AutomationId = "ClearButton"
};

clearButton.Clicked += (s, e) =>
{
_searchBar.Text = string.Empty;
};

Content = new VerticalStackLayout
{
Padding = new Thickness(20),
Spacing = 10,
Children =
{
_searchBar,
addTextButton,
clearButton
}
};
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue34422 : _IssuesUITest
{
public Issue34422(TestDevice device) : base(device) { }

public override string Issue => "SearchBar clear button still appears on MacCatalyst after clearing input";

[Test, Order(1)]
[Category(UITestCategories.SearchBar)]
public void SearchBarClearButtonShouldBeVisibleWithText()
{
App.WaitForElement("TestSearchBar");
App.Tap("TestSearchBar");
App.Tap("AddTextButton");
VerifyScreenshot();

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] Regression Prevention and Test Coverage — These new screenshot assertions are strict VerifyScreenshot() calls, but the PR branch still fails both Issue34422 MacCatalyst screenshots with visual baseline mismatches. The verified passing candidate refreshed the Mac baselines and used a small rendering tolerance (for example VerifyScreenshot(tolerance: 1)). Please update this assertion and the matching assertion later in the file so the regression test can actually pass reliably while still checking the clear-button state.

}
Comment on lines +15 to +21

[Test, Order(2)]
[Category(UITestCategories.SearchBar)]
public void SearchBarClearButtonShouldDisappearAfterClearingInput()
{
// First add text so the clear button appears
App.WaitForElement("TestSearchBar");
App.Tap("TestSearchBar");
App.Tap("AddTextButton");
App.Tap("ClearButton");

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] Regression Prevention and Test Coverage — This regression test clears the SearchBar through a helper Button, so it does not exercise the native MacCatalyst clear affordance whose stale rendering is being fixed. Add coverage that enters text and activates/verifies the native clear button path, or expose/assert the native clear-button state from the handler, so the test fails for the current Hidden-toggle implementation and passes for the hierarchy-refresh fix.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since this fix does not use the remove/re-add approach for the native clear button, the proposed test coverage is not applicable to the current implementation.

VerifyScreenshot();
}
Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ protected override MauiSearchBar CreatePlatformView()

_editor = searchBar.GetSearchTextField();

Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.

return searchBar;
}

Expand Down Expand Up @@ -154,6 +153,9 @@ internal static void MapSelectionLength(ISearchBarHandler handler, ISearchBar se
public static void MapCancelButtonColor(ISearchBarHandler handler, ISearchBar searchBar)
{
handler.PlatformView?.UpdateCancelButton(searchBar);
if (handler is SearchBarHandler searchBarHandler)
handler.PlatformView?.UpdateClearButtonVisibility(!string.IsNullOrEmpty(searchBar.Text));

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] Handler Mapper and Property Patterns — The clear-button visibility refresh is wired through MapCancelButtonColor, but text changes are owned by MapText/TextSetOrChanged, not the cancel-button-color mapper. If ShowsCancelButton is already in the expected state, a programmatic Text mutation can skip this mapper and leave the MacCatalyst clear button stale. Move the refresh to the actual text update paths and keep MapCancelButtonColor scoped to cancel-button styling.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As per the suggestion, the Clear Button remains visible while the SearchBar is focused, which is not required. Therefore, not taking this suggestion, as the current implementation works correctly.


Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.
}
Comment on lines 153 to 159

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] Handler Mapper and Property Patterns - This wires the MacCatalyst clear-button refresh through MapCancelButtonColor, so programmatic Text updates only reach it when UpdateCancelButtonVisibility() decides the cancel-button state changed. The native clear affordance is separate from the cancel button; if the text is cleared while ShowsCancelButton already matches the desired state, this refresh is skipped and the stale clear button can remain visible. Please run the clear-button refresh from the actual text mutation paths (MapText / TextSetOrChanged / editing changed) instead of piggybacking on the cancel-button color mapper.

Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.
internal static void MapSearchIconColor(ISearchBarHandler handler, ISearchBar searchBar)
Expand Down Expand Up @@ -277,6 +279,7 @@ void OnEditingChanged(object? sender, EventArgs e)
if (Handler is SearchBarHandler handler)
{

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.

This only updates the MacCatalyst clear button from native editing callbacks. The failing repro clears the managed SearchBar.Text programmatically, which flows through MapText/UpdateText and does not raise this callback, so the native clear button can remain visible after the text is cleared. Please update the clear-button state from the text mapper as well.

handler.UpdateCancelButtonVisibility();
handler.PlatformView?.UpdateClearButtonVisibility(!string.IsNullOrEmpty(VirtualView?.Text));
}
}

Expand Down
20 changes: 20 additions & 0 deletions src/Core/src/Platform/iOS/SearchBarExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,24 @@ public static void UpdateFont(this UISearchBar uiSearchBar, ITextStyle textStyle
textField.UpdateFont(textStyle, fontManager);
}

internal static void UpdateClearButtonVisibility(this UISearchBar uiSearchBar, bool hasText)
{
if (OperatingSystem.IsMacCatalyst())
{
var clearButton = uiSearchBar.GetClearButton();

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.

This relies on private KVC (clearButton) and a cached UIKit subview at the moment this method runs. On MacCatalyst UIKit can create or refresh this control outside these callbacks, so this is fragile and was not enough to remove the Clear text element in the regression evidence. Prefer a supported text-field mode or a lifecycle-aware update tied to MapText, and keep the private lookup as a last resort only if it is empirically proven.


if (clearButton != null)
{
var shouldHide = !hasText;

if (clearButton.Hidden != shouldHide)
{
clearButton.Hidden = shouldHide;
Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.

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] Logic and Correctness — Toggling the private clear button's Hidden flag is not sufficient for the MacCatalyst failure mode: the supplied gate result shows the PR fix still fails, while the verified alternative removes/re-adds the private clear UIButton from its superview to force UIKit to refresh the rendered state. Please replace this with the empirically passing hierarchy-refresh approach, or another mechanism that actually invalidates the native clear button.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since the clear button only needs its visibility updated in this scenario, removing and re-adding it to the hierarchy is not required. The current visibility toggle approach is sufficient for the fix.

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] iOS/macCatalyst Platform Specifics - Setting the private clear UIButton.Hidden flag is not enough to invalidate the rendered MacCatalyst clear affordance; the supplied gate for this PR still failed with the clear button visible after Text was cleared. The passing local alternative forced UIKit to rebuild the affordance by removing the native clear button from its superview when hasText is false. Please replace the Hidden toggle with an invalidation mechanism proven against the Issue34422 MacCatalyst scenario.

Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.
}
}
}
}

public static void UpdateVerticalTextAlignment(this UISearchBar uiSearchBar, ISearchBar searchBar)
{
uiSearchBar.UpdateVerticalTextAlignment(searchBar, null);
Expand Down Expand Up @@ -460,5 +478,7 @@ static UITextPosition GetSelectionEnd(UITextField textField, UITextPosition star
var end = textField.GetPosition(start, endOffset - startOffset);
return end ?? start;
}
internal static UIButton? GetClearButton(this UISearchBar searchBar) =>
searchBar.GetSearchTextField()?.ValueForKey(new NSString("clearButton")) as UIButton;
Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.

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] Performance-Critical Path / Memory Leak PreventionGetClearButton() is called from text-change handling and mapper callbacks, so new NSString("clearButton") allocates a native object on the SearchBar input path each time the clear button is queried. Please reuse a static readonly NSString key (or otherwise avoid per-call native allocation) before shipping this as part of every MacCatalyst SearchBar text update.

}
}
Loading