Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ protected virtual void LoadView(SearchHandler searchHandler)

int padding = (int)context.ToPixels(8);

_searchButton = CreateImageButton(context, searchHandler, SearchHandler.QueryIconProperty, Resource.Drawable.abc_ic_search_api_material, padding, 0, "SearchIcon");
_searchButton = CreateImageButton(context, searchHandler, SearchHandler.QueryIconProperty, Resource.Drawable.abc_ic_search_api_material, padding, 0, "SearchIcon", searchHandler.TextColor?.ToPlatform());

lp = new LinearLayout.LayoutParams(0, LP.MatchParent)
{
Expand All @@ -211,8 +211,8 @@ protected virtual void LoadView(SearchHandler searchHandler)
// A note on accessibility. The _textBlocks hint is what android defaults to reading in the screen
// reader. Therefore, we do not need to set something else.

_clearButton = CreateImageButton(context, searchHandler, SearchHandler.ClearIconProperty, Resource.Drawable.abc_ic_clear_material, 0, padding, nameof(SearchHandler.ClearIcon));
_clearPlaceholderButton = CreateImageButton(context, searchHandler, SearchHandler.ClearPlaceholderIconProperty, -1, 0, padding, nameof(SearchHandler.ClearPlaceholderIcon));
_clearButton = CreateImageButton(context, searchHandler, SearchHandler.ClearIconProperty, Resource.Drawable.abc_ic_clear_material, 0, padding, nameof(SearchHandler.ClearIcon), searchHandler.CancelButtonColor?.ToPlatform());
_clearPlaceholderButton = CreateImageButton(context, searchHandler, SearchHandler.ClearPlaceholderIconProperty, -1, 0, padding, nameof(SearchHandler.ClearPlaceholderIcon), searchHandler.TextColor?.ToPlatform());

linearLayout.AddView(_searchButton);
linearLayout.AddView(_textBlock);
Expand Down Expand Up @@ -245,12 +245,62 @@ protected virtual void OnSearchHandlerPropertyChanged(object sender, PropertyCha
{
_textBlock.Enabled = SearchHandler.IsSearchEnabled;
}
else if (e.PropertyName == SearchHandler.ClearPlaceholderEnabledProperty.PropertyName)
else if (e.PropertyName == SearchHandler.QueryIconProperty.PropertyName)
{
ApplyImageSource(_searchButton, SearchHandler.QueryIcon, Resource.Drawable.abc_ic_search_api_material, SearchHandler.TextColor?.ToPlatform());
}
else if (e.PropertyName == SearchHandler.ClearIconProperty.PropertyName)
{
ApplyImageSource(_clearButton, SearchHandler.ClearIcon, Resource.Drawable.abc_ic_clear_material, SearchHandler.CancelButtonColor?.ToPlatform());
}
else if (e.PropertyName == SearchHandler.ClearPlaceholderIconProperty.PropertyName)
{
ApplyImageSource(_clearPlaceholderButton, SearchHandler.ClearPlaceholderIcon, -1, SearchHandler.TextColor?.ToPlatform());
UpdateClearButtonState();
}
}

void ApplyImageSource(AImageButton button, ImageSource image, int defaultImage, AColor? tint = null)
{
if (button is null)
{
return;
}

void ApplyTint()
{
if (tint.HasValue)
{
button.Drawable?.Mutate();
button.Drawable?.SetColorFilter(tint.Value, FilterMode.SrcIn);
}
}

if (image is not null)
{
AutomationPropertiesProvider.SetContentDescription(button, image, null, null);
image.LoadImage(MauiContext, (r) =>
{
if (_disposed)
{
return;
}

button.SetImageDrawable(r?.Value);
ApplyTint();
});
}
else if (defaultImage > 0 && ContextCompat.GetDrawable(Context, defaultImage) is Drawable defaultDrawable)
{
button.SetImageDrawable(defaultDrawable);
ApplyTint();
}
else
{
button.SetImageDrawable(null);
}
}

protected override async void OnAttachedToWindow()
{
base.OnAttachedToWindow();
Expand Down Expand Up @@ -317,31 +367,15 @@ protected virtual void OnSearchButtonClicked(object sender, EventArgs e)
{
}

AImageButton CreateImageButton(Context context, BindableObject bindable, BindableProperty property, int defaultImage, int leftMargin, int rightMargin, string tag)
AImageButton CreateImageButton(Context context, BindableObject bindable, BindableProperty property, int defaultImage, int leftMargin, int rightMargin, string tag, AColor? tint = null)
{
var result = new AImageButton(context);
result.Tag = tag;
result.SetPadding(0, 0, 0, 0);
result.Focusable = false;
result.SetScaleType(ImageView.ScaleType.FitCenter);

if (bindable.GetValue(property) is ImageSource image)
{
AutomationPropertiesProvider.SetContentDescription(result, image, null, null);

image.LoadImage(MauiContext, (r) =>
{
result.SetImageDrawable(r?.Value);
});
}
else if (defaultImage > 0 && ContextCompat.GetDrawable(Context, defaultImage) is Drawable defaultDrawable)
{
result.SetImageDrawable(defaultDrawable);
}
else
{
result.SetImageDrawable(null);
}
ApplyImageSource(result, bindable.GetValue(property) as ImageSource, defaultImage, tint);

var lp = new LinearLayout.LayoutParams((int)Context.ToPixels(22), LP.MatchParent)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,18 @@ protected virtual void OnSearchHandlerPropertyChanged(object sender, PropertyCha
{
UpdateAutomationId();
}
else if (e.PropertyName == SearchHandler.QueryIconProperty.PropertyName)
{
UpdateSearchBarIcon(_searchController.SearchBar, _searchHandler.QueryIcon, UISearchBarIcon.Search);
}
else if (e.PropertyName == SearchHandler.ClearIconProperty.PropertyName)
{
UpdateSearchBarIcon(_searchController.SearchBar, _searchHandler.ClearIcon, UISearchBarIcon.Clear);
}
else if (e.PropertyName == SearchHandler.ClearPlaceholderIconProperty.PropertyName)
{
UpdateSearchBarIcon(_searchController.SearchBar, _searchHandler.ClearPlaceholderIcon, UISearchBarIcon.Bookmark);
}
}

void UpdateAutomationId()
Expand Down Expand Up @@ -1179,10 +1191,59 @@ void SetSearchBarIcon(UISearchBar searchBar, ImageSource source, UISearchBarIcon
searchBar.SetImageforSearchBarIcon(newResult, icon, UIControlState.Normal);
searchBar.SetImageforSearchBarIcon(newResult, icon, UIControlState.Highlighted);
searchBar.SetImageforSearchBarIcon(newResult, icon, UIControlState.Selected);

// iOS caches the clear button image once it has been shown. After the button
// has appeared (user typed text), SetImageforSearchBarIcon alone won't refresh
// it. Directly update the button subview so dynamic changes are reflected.
if (icon is UISearchBarIcon.Clear)
{
UpdateClearButtonImage(searchBar, newResult);
}
}
});
}

// Directly updates the clear button (X) inside UISearchBar's UITextField subview.
// This is required because iOS does not re-apply SetImageforSearchBarIcon to a
// clear button that is already visible on screen.
//
// NOTE: "searchField" and "clearButton" are private UIKit KVC keys. Apple does not
// expose these as public API. They have been stable across iOS versions and are a
// well-established pattern in Xamarin/MAUI, but could break in a future OS release.
static void UpdateClearButtonImage(UISearchBar searchBar, UIImage? image)
{
if (searchBar.ValueForKey(new NSString("searchField")) is UITextField textField &&
textField.ValueForKey(new NSString("clearButton")) is UIButton clearButton)
{
clearButton.SetImage(image, UIControlState.Normal);
clearButton.SetImage(image, UIControlState.Highlighted);
}
}

void UpdateSearchBarIcon(UISearchBar searchBar, ImageSource? source, UISearchBarIcon icon)
{
if (source is not null)
{
SetSearchBarIcon(searchBar, source, icon);
}
else
{
// Reset to default system icon by clearing the custom image
searchBar.SetImageforSearchBarIcon(null, icon, UIControlState.Normal);
searchBar.SetImageforSearchBarIcon(null, icon, UIControlState.Highlighted);
searchBar.SetImageforSearchBarIcon(null, icon, UIControlState.Selected);

if (icon is UISearchBarIcon.Clear)
{
// UIKit caches the clear button image once it is on-screen, so
// SetImageforSearchBarIcon(null, ...) alone will not update the visible
// button. Restore the system default SF Symbol so the button shows the
// standard 'X' instead of becoming imageless.
UpdateClearButtonImage(searchBar, UIImage.GetSystemImage("multiply.circle.fill"));
}
}
}

void OnPageLoaded(object? sender, EventArgs e)
{
if (sender is Page page)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,12 @@ void OnCurrentSearchHandlerPropertyChanged(object? sender, PropertyChangedEventA
case nameof(SearchHandler.VerticalTextAlignment):
autoSuggestBox.UpdateSearchHandlerVerticalTextAlignment(_currentSearchHandler);
break;
case nameof(SearchHandler.QueryIcon):
UpdateQueryIcon();
break;
// TODO: ClearIcon and ClearPlaceholderIcon are not supported on Windows
// (AutoSuggestBox has no built-in clear/placeholder icon API).
// Tracked in: https://github.com/dotnet/maui/issues/28619
}
}

Expand Down
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.
146 changes: 146 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue35736.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 35736, "SearchHandler QueryIcon, ClearIcon, ClearPlaceholderIcon need to update visually at runtime", PlatformAffected.iOS | PlatformAffected.Android | PlatformAffected.UWP)]
public class Issue35736 : Shell
{
public Issue35736()
{
var page = new Issue35736Page();
Items.Add(new ShellContent
{
Title = "Search Icon Test",
Content = page
});
}

public class Issue35736Page : ContentPage
{
readonly SearchHandler _searchHandler;
bool _useAltQueryIcon;
bool _useAltClearIcon;
bool _useAltClearPlaceholderIcon;
bool _clearPlaceholderEnabled = true;

readonly Label _queryIconLabel;
readonly Label _clearIconLabel;
readonly Label _clearPlaceholderIconLabel;
readonly Label _clearPlaceholderEnabledLabel;
readonly Button _toggleClearPlaceholderEnabledBtn;

public Issue35736Page()
{
Title = "Search Icon Test";

_searchHandler = new SearchHandler
{
Placeholder = "Search items...",
AutomationId = "Issue35736SearchHandler",
QueryIcon = ImageSource.FromFile("bank.png"),
ClearIcon = ImageSource.FromFile("bank.png"),
ClearPlaceholderIcon = ImageSource.FromFile("bank.png"),
ClearPlaceholderEnabled = true,
ShowsResults = false,
};

Shell.SetSearchHandler(this, _searchHandler);

_queryIconLabel = new Label { AutomationId = "Issue35736QueryIconLabel", Text = "QueryIcon: bank.png" };
_clearIconLabel = new Label { AutomationId = "Issue35736ClearIconLabel", Text = "ClearIcon: bank.png" };
_clearPlaceholderIconLabel = new Label { AutomationId = "Issue35736ClearPlaceholderIconLabel", Text = "ClearPlaceholderIcon: bank.png" };
_clearPlaceholderEnabledLabel = new Label { AutomationId = "Issue35736ClearPlaceholderEnabledLabel", Text = "ClearPlaceholderEnabled: True" };

var toggleQueryIconBtn = new Button { Text = "Toggle QueryIcon", AutomationId = "Issue35736ToggleQueryIcon" };
toggleQueryIconBtn.Clicked += OnToggleQueryIcon;

var toggleClearIconBtn = new Button { Text = "Toggle ClearIcon", AutomationId = "Issue35736ToggleClearIcon" };
toggleClearIconBtn.Clicked += OnToggleClearIcon;

var toggleClearPlaceholderIconBtn = new Button { Text = "Toggle ClearPlaceholderIcon", AutomationId = "Issue35736ToggleClearPlaceholderIcon" };
toggleClearPlaceholderIconBtn.Clicked += OnToggleClearPlaceholderIcon;

_toggleClearPlaceholderEnabledBtn = new Button
{
Text = "Toggle ClearPlaceholderEnabled (Current: True)",
AutomationId = "Issue35736ToggleClearPlaceholderEnabled"
};
_toggleClearPlaceholderEnabledBtn.Clicked += OnToggleClearPlaceholderEnabled;

var resetBtn = new Button { Text = "Reset All to Defaults", AutomationId = "Issue35736ResetAll" };
resetBtn.Clicked += OnResetAll;

Content = new ScrollView
{
Content = new VerticalStackLayout
{
Padding = new Thickness(30, 0),
Spacing = 15,
Children =
{
new Label { Text = "SearchHandler Icon Demo", FontSize = 20, FontAttributes = FontAttributes.Bold, HorizontalOptions = LayoutOptions.Center },
new VerticalStackLayout
{
Spacing = 4,
Children = { _queryIconLabel, _clearIconLabel, _clearPlaceholderIconLabel, _clearPlaceholderEnabledLabel }
},
toggleQueryIconBtn,
toggleClearIconBtn,
toggleClearPlaceholderIconBtn,
_toggleClearPlaceholderEnabledBtn,
resetBtn,
}
}
};
}

void OnToggleQueryIcon(object sender, EventArgs e)
{
_useAltQueryIcon = !_useAltQueryIcon;
var icon = _useAltQueryIcon ? "calculator.png" : "bank.png";
_searchHandler.QueryIcon = ImageSource.FromFile(icon);
_queryIconLabel.Text = $"QueryIcon: {icon}";
}

void OnToggleClearIcon(object sender, EventArgs e)
{
_useAltClearIcon = !_useAltClearIcon;
var icon = _useAltClearIcon ? "calculator.png" : "bank.png";
_searchHandler.ClearIcon = ImageSource.FromFile(icon);
_clearIconLabel.Text = $"ClearIcon: {icon}";
}

void OnToggleClearPlaceholderIcon(object sender, EventArgs e)
{
_useAltClearPlaceholderIcon = !_useAltClearPlaceholderIcon;
var icon = _useAltClearPlaceholderIcon ? "calculator.png" : "bank.png";
_searchHandler.ClearPlaceholderIcon = ImageSource.FromFile(icon);
_clearPlaceholderIconLabel.Text = $"ClearPlaceholderIcon: {icon}";
}

void OnToggleClearPlaceholderEnabled(object sender, EventArgs e)
{
_clearPlaceholderEnabled = !_clearPlaceholderEnabled;
_searchHandler.ClearPlaceholderEnabled = _clearPlaceholderEnabled;
_clearPlaceholderEnabledLabel.Text = $"ClearPlaceholderEnabled: {_clearPlaceholderEnabled}";
_toggleClearPlaceholderEnabledBtn.Text = $"Toggle ClearPlaceholderEnabled (Current: {_clearPlaceholderEnabled})";
}

void OnResetAll(object sender, EventArgs e)
{
_useAltQueryIcon = false;
_useAltClearIcon = false;
_useAltClearPlaceholderIcon = false;
_clearPlaceholderEnabled = true;

_searchHandler.QueryIcon = null;
_searchHandler.ClearIcon = null;
_searchHandler.ClearPlaceholderIcon = null;
_searchHandler.ClearPlaceholderEnabled = true;

_queryIconLabel.Text = "QueryIcon: default";
_clearIconLabel.Text = "ClearIcon: default";
_clearPlaceholderIconLabel.Text = "ClearPlaceholderIcon: default";
_clearPlaceholderEnabledLabel.Text = "ClearPlaceholderEnabled: True";
_toggleClearPlaceholderEnabledBtn.Text = "Toggle ClearPlaceholderEnabled (Current: True)";
}
}
}
Loading
Loading