diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7a5a83283..9fda1551e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## [55.5.0]
+- [iOS][BottomSheet] Replaced MAUI SearchBar with native UISearchController for bottom sheet search, integrating the search bar into the navigation bar
+- [Android][BottomSheet] Replaced MAUI SearchBar with Material 3 SearchBar and SearchView components for bottom sheet search
+- [ContentPage] Added `SearchBehavior` property for native platform search on regular pages (iOS: UISearchController, Android: Material 3 SearchBar + SearchView)
+
## [55.4.0]
- [iOS][BottomSheet] Use native UINavigationBar for bottom sheet header with centered title, system close/back buttons, and proper blur behavior
- [Android][BottomSheet] Fixed edge-to-edge constraints not applying until scroll when start Positioning is Large
diff --git a/src/app/Components/ComponentsSamples/BottomSheets/BottomSheetSamples.xaml b/src/app/Components/ComponentsSamples/BottomSheets/BottomSheetSamples.xaml
index 966232d2d..c5b902f8c 100644
--- a/src/app/Components/ComponentsSamples/BottomSheets/BottomSheetSamples.xaml
+++ b/src/app/Components/ComponentsSamples/BottomSheets/BottomSheetSamples.xaml
@@ -25,6 +25,12 @@
+
+
\ No newline at end of file
diff --git a/src/app/Components/ComponentsSamples/BottomSheets/Sheets/BottomSheetWithSearch.xaml b/src/app/Components/ComponentsSamples/BottomSheets/Sheets/BottomSheetWithSearch.xaml
new file mode 100644
index 000000000..56bc0e618
--- /dev/null
+++ b/src/app/Components/ComponentsSamples/BottomSheets/Sheets/BottomSheetWithSearch.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/Components/ComponentsSamples/BottomSheets/Sheets/BottomSheetWithSearch.xaml.cs b/src/app/Components/ComponentsSamples/BottomSheets/Sheets/BottomSheetWithSearch.xaml.cs
new file mode 100644
index 000000000..105357a7f
--- /dev/null
+++ b/src/app/Components/ComponentsSamples/BottomSheets/Sheets/BottomSheetWithSearch.xaml.cs
@@ -0,0 +1,9 @@
+namespace Components.ComponentsSamples.BottomSheets.Sheets;
+
+public partial class BottomSheetWithSearch
+{
+ public BottomSheetWithSearch()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/src/app/Components/ComponentsSamples/BottomSheets/Sheets/BottomSheetWithSearchViewModel.cs b/src/app/Components/ComponentsSamples/BottomSheets/Sheets/BottomSheetWithSearchViewModel.cs
new file mode 100644
index 000000000..9308807b5
--- /dev/null
+++ b/src/app/Components/ComponentsSamples/BottomSheets/Sheets/BottomSheetWithSearchViewModel.cs
@@ -0,0 +1,40 @@
+using System.Windows.Input;
+using Components.SampleData;
+using DIPS.Mobile.UI.MVVM;
+
+namespace Components.ComponentsSamples.BottomSheets.Sheets;
+
+public class BottomSheetWithSearchViewModel : ViewModel
+{
+ private List m_people;
+ private readonly List m_originalPeople;
+
+ public BottomSheetWithSearchViewModel()
+ {
+ People = SampleDataStorage.People.ToList();
+ m_originalPeople = People.ToList();
+ SearchCommand = new Command(FilterItems);
+ }
+
+ private void FilterItems(string filterText)
+ {
+ if (string.IsNullOrEmpty(filterText))
+ {
+ People = m_originalPeople.ToList();
+ }
+ else
+ {
+ People = m_originalPeople
+ .Where(p => p.DisplayName.ToLower().Contains(filterText.ToLower()))
+ .ToList();
+ }
+ }
+
+ public List People
+ {
+ get => m_people;
+ set => RaiseWhenSet(ref m_people, value);
+ }
+
+ public ICommand SearchCommand { get; }
+}
diff --git a/src/app/Components/ComponentsSamples/Searching/NativeSearchPageSamples.xaml b/src/app/Components/ComponentsSamples/Searching/NativeSearchPageSamples.xaml
new file mode 100644
index 000000000..01068470f
--- /dev/null
+++ b/src/app/Components/ComponentsSamples/Searching/NativeSearchPageSamples.xaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/Components/ComponentsSamples/Searching/NativeSearchPageSamples.xaml.cs b/src/app/Components/ComponentsSamples/Searching/NativeSearchPageSamples.xaml.cs
new file mode 100644
index 000000000..f8a633d15
--- /dev/null
+++ b/src/app/Components/ComponentsSamples/Searching/NativeSearchPageSamples.xaml.cs
@@ -0,0 +1,9 @@
+namespace Components.ComponentsSamples.Searching;
+
+public partial class NativeSearchPageSamples
+{
+ public NativeSearchPageSamples()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/src/app/Components/ComponentsSamples/Searching/NativeSearchPageSamplesViewModel.cs b/src/app/Components/ComponentsSamples/Searching/NativeSearchPageSamplesViewModel.cs
new file mode 100644
index 000000000..1ef202a62
--- /dev/null
+++ b/src/app/Components/ComponentsSamples/Searching/NativeSearchPageSamplesViewModel.cs
@@ -0,0 +1,40 @@
+using System.Windows.Input;
+using Components.SampleData;
+using DIPS.Mobile.UI.MVVM;
+
+namespace Components.ComponentsSamples.Searching;
+
+public class NativeSearchPageSamplesViewModel : ViewModel
+{
+ private List m_people;
+ private readonly List m_originalPeople;
+
+ public NativeSearchPageSamplesViewModel()
+ {
+ People = SampleDataStorage.People.ToList();
+ m_originalPeople = People.ToList();
+ SearchCommand = new Command(FilterItems);
+ }
+
+ private void FilterItems(string filterText)
+ {
+ if (string.IsNullOrEmpty(filterText))
+ {
+ People = m_originalPeople.ToList();
+ }
+ else
+ {
+ People = m_originalPeople
+ .Where(p => p.DisplayName.ToLower().Contains(filterText.ToLower()))
+ .ToList();
+ }
+ }
+
+ public List People
+ {
+ get => m_people;
+ set => RaiseWhenSet(ref m_people, value);
+ }
+
+ public ICommand SearchCommand { get; }
+}
diff --git a/src/app/Components/ComponentsSamples/Searching/SearchingSamples.xaml b/src/app/Components/ComponentsSamples/Searching/SearchingSamples.xaml
index 4117f07f4..fbbae4ebf 100644
--- a/src/app/Components/ComponentsSamples/Searching/SearchingSamples.xaml
+++ b/src/app/Components/ComponentsSamples/Searching/SearchingSamples.xaml
@@ -11,6 +11,10 @@
HasBottomDivider="True"/>
+ Command="{helpers:NavigationCommand {x:Type searching:SearchPageSamples}}"
+ HasBottomDivider="True" />
+
+
\ No newline at end of file
diff --git a/src/library/DIPS.Mobile.UI/Components/BottomSheets/Android/BottomSheetHandler.cs b/src/library/DIPS.Mobile.UI/Components/BottomSheets/Android/BottomSheetHandler.cs
index 3e5d0ab41..46b0d0e8e 100644
--- a/src/library/DIPS.Mobile.UI/Components/BottomSheets/Android/BottomSheetHandler.cs
+++ b/src/library/DIPS.Mobile.UI/Components/BottomSheets/Android/BottomSheetHandler.cs
@@ -19,6 +19,7 @@
using Paint = Android.Graphics.Paint;
using System.ComponentModel;
using DIPS.Mobile.UI.API.Library;
+using DIPS.Mobile.UI.Components.BottomSheets.Android;
using DIPS.Mobile.UI.Components.BottomSheets.Header;
using SearchBar = DIPS.Mobile.UI.Components.Searching.SearchBar;
using View = Microsoft.Maui.Controls.View;
@@ -35,7 +36,7 @@ public partial class BottomSheetHandler : ContentViewHandler
internal AView? m_bottomBar;
private static AView? s_mEmptyNonFitToContentView;
- private AView? m_searchBarView;
+ private BottomSheetSearchField? m_searchField;
private BottomSheetHeader m_bottomSheetHeader;
private List> m_weakSearchBars = [];
private WeakReference? m_weakCurrentFocusedSearchBar;
@@ -88,9 +89,15 @@ public AView OnBeforeOpening(IMauiContext mauiContext, Context context, AView bo
m_bottomSheetHeader = new BottomSheetHeader(m_bottomSheet);
bottomSheetLayout.AddView(m_bottomSheetHeader.ToPlatform(mauiContext));
- m_searchBarView = m_bottomSheet.SearchBar.ToPlatform(mauiContext);
- bottomSheetLayout.AddView(m_searchBarView);
+ // Create native Material 3 search field
+ m_searchField = new BottomSheetSearchField(context, m_bottomSheet);
+ bottomSheetLayout.AddView(m_searchField.View);
ToggleSearchBar();
+
+ // Set focus/unfocus actions on the BottomSheet
+ m_bottomSheet.FocusSearchAction = () => m_searchField?.Focus();
+ m_bottomSheet.UnfocusSearchAction = () => m_searchField?.Unfocus();
+
FindAndSetupSearchBars();
bottomSheetLayout.AddView(bottomSheetAndroidView);
@@ -134,23 +141,15 @@ private void FindAndSetupSearchBars()
searchBar.Focused += SearchBarOnFocused;
searchBar.Unfocused += SearchBarOnUnfocused;
}
-
- // Also, setup the internal search bar in BottomSheet
- if (m_bottomSheet.SearchBar is { } searchBarInternal)
- {
- searchBarInternal.Focused += SearchBarOnFocused;
- searchBarInternal.Unfocused += SearchBarOnUnfocused;
- }
-
}
private void ToggleSearchBar()
{
- if (m_searchBarView == null)
+ if (m_searchField?.View == null)
return;
- m_searchBarView.Visibility = m_bottomSheet.HasSearchBar ? ViewStates.Visible : ViewStates.Gone;
+ m_searchField.View.Visibility = m_bottomSheet.HasSearchBar ? ViewStates.Visible : ViewStates.Gone;
}
private void SearchBarOnUnfocused(object? sender, EventArgs e)
@@ -246,12 +245,11 @@ protected override void DisconnectHandler(ContentViewGroup platformView)
searchBar.Unfocused -= SearchBarOnUnfocused;
}
- // Also, dispose the internal search bar in BottomSheet
- if (m_bottomSheet.SearchBar is { } searchBarInternal)
- {
- searchBarInternal.Focused -= SearchBarOnFocused;
- searchBarInternal.Unfocused -= SearchBarOnUnfocused;
- }
+ // Clean up native search field
+ m_searchField?.Cleanup();
+ m_searchField = null;
+ m_bottomSheet.FocusSearchAction = null;
+ m_bottomSheet.UnfocusSearchAction = null;
}
///
diff --git a/src/library/DIPS.Mobile.UI/Components/BottomSheets/Android/BottomSheetSearchField.cs b/src/library/DIPS.Mobile.UI/Components/BottomSheets/Android/BottomSheetSearchField.cs
new file mode 100644
index 000000000..50b3feacd
--- /dev/null
+++ b/src/library/DIPS.Mobile.UI/Components/BottomSheets/Android/BottomSheetSearchField.cs
@@ -0,0 +1,124 @@
+using Android.Content;
+using Android.Text;
+using Android.Views;
+using Android.Views.InputMethods;
+using Android.Widget;
+using AView = Android.Views.View;
+using MaterialSearchBar = Google.Android.Material.Search.SearchBar;
+using MaterialSearchView = Google.Android.Material.Search.SearchView;
+
+namespace DIPS.Mobile.UI.Components.BottomSheets.Android;
+
+///
+/// A Material 3 search component for BottomSheets using the actual
+/// and components.
+/// The SearchBar provides the collapsed pill-shaped search trigger,
+/// while the SearchView provides the expanded search input with EditText.
+///
+internal class BottomSheetSearchField : Java.Lang.Object, ITextWatcher, MaterialSearchView.ITransitionListener
+{
+ private readonly WeakReference m_weakBottomSheet;
+ private readonly MaterialSearchBar m_searchBar;
+ private readonly MaterialSearchView m_searchView;
+ private readonly FrameLayout m_container;
+ private string m_previousText = string.Empty;
+
+ public BottomSheetSearchField(Context context, BottomSheet bottomSheet)
+ {
+ m_weakBottomSheet = new WeakReference(bottomSheet);
+
+ // Create wrapper container
+ m_container = new FrameLayout(context);
+ m_container.LayoutParameters = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MatchParent,
+ ViewGroup.LayoutParams.WrapContent);
+
+ // Material 3 SearchBar (pill-shaped search trigger)
+ m_searchBar = new MaterialSearchBar(context);
+ m_searchBar.LayoutParameters = new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MatchParent,
+ ViewGroup.LayoutParams.WrapContent);
+ m_container.AddView(m_searchBar);
+
+ // Material 3 SearchView (expanded search with EditText)
+ m_searchView = new MaterialSearchView(context);
+ m_searchView.LayoutParameters = new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MatchParent,
+ ViewGroup.LayoutParams.MatchParent);
+
+ // Connect SearchView to SearchBar for proper M3 transitions
+ m_searchView.SetupWithSearchBar(m_searchBar);
+
+ // Listen for text changes on the SearchView's EditText
+ m_searchView.EditText.AddTextChangedListener(this);
+
+ // Listen for transition events (show/hide)
+ m_searchView.AddTransitionListener(this);
+
+ m_container.AddView(m_searchView);
+ }
+
+ public AView View => m_container;
+
+ ///
+ /// Returns the SearchBar for external layout if needed.
+ ///
+ public MaterialSearchBar SearchBar => m_searchBar;
+
+ public void Focus()
+ {
+ m_searchView.Show();
+ m_searchView.EditText.RequestFocus();
+ var imm = (InputMethodManager?)m_searchView.EditText.Context?.GetSystemService(Context.InputMethodService);
+ imm?.ShowSoftInput(m_searchView.EditText, ShowFlags.Implicit);
+ }
+
+ public void Unfocus()
+ {
+ var imm = (InputMethodManager?)m_searchView.EditText.Context?.GetSystemService(Context.InputMethodService);
+ imm?.HideSoftInputFromWindow(m_searchView.EditText.WindowToken, 0);
+ m_searchView.Hide();
+ }
+
+ // ITextWatcher implementation
+ public void AfterTextChanged(IEditable? s)
+ {
+ var newText = s?.ToString() ?? string.Empty;
+
+ if (!m_weakBottomSheet.TryGetTarget(out var bottomSheet))
+ return;
+
+ bottomSheet.OnNativeSearchTextChanged(newText, m_previousText);
+ m_previousText = newText;
+ }
+
+ public void BeforeTextChanged(Java.Lang.ICharSequence? s, int start, int count, int after)
+ {
+ }
+
+ public void OnTextChanged(Java.Lang.ICharSequence? s, int start, int before, int count)
+ {
+ }
+
+ // MaterialSearchView.ITransitionListener implementation
+ public void OnStateChanged(MaterialSearchView searchView, MaterialSearchView.TransitionState previousState, MaterialSearchView.TransitionState newState)
+ {
+ if (!m_weakBottomSheet.TryGetTarget(out var bottomSheet))
+ return;
+
+ if (newState == MaterialSearchView.TransitionState.Shown)
+ {
+ bottomSheet.OnSearchFieldFocused();
+ }
+ else if (newState == MaterialSearchView.TransitionState.Hidden)
+ {
+ bottomSheet.OnSearchFieldUnfocused();
+ }
+ }
+
+ public void Cleanup()
+ {
+ m_searchView.EditText.RemoveTextChangedListener(this);
+ m_searchView.RemoveTransitionListener(this);
+ }
+}
diff --git a/src/library/DIPS.Mobile.UI/Components/BottomSheets/BottomSheet.Properties.cs b/src/library/DIPS.Mobile.UI/Components/BottomSheets/BottomSheet.Properties.cs
index b693eb819..f9c427887 100644
--- a/src/library/DIPS.Mobile.UI/Components/BottomSheets/BottomSheet.Properties.cs
+++ b/src/library/DIPS.Mobile.UI/Components/BottomSheets/BottomSheet.Properties.cs
@@ -63,7 +63,9 @@ public ICommand? OnBackButtonPressedCommand
}
///
- /// Determines if the bottom sheet should have a at the top
+ /// Determines if the bottom sheet should have a search bar at the top.
+ /// On iOS, this uses a native UISearchController integrated with the navigation bar.
+ /// On Android, this uses a Material 3 styled native search field.
///
public bool HasSearchBar
{
diff --git a/src/library/DIPS.Mobile.UI/Components/BottomSheets/BottomSheet.cs b/src/library/DIPS.Mobile.UI/Components/BottomSheets/BottomSheet.cs
index e662c280d..852ec6ee0 100644
--- a/src/library/DIPS.Mobile.UI/Components/BottomSheets/BottomSheet.cs
+++ b/src/library/DIPS.Mobile.UI/Components/BottomSheets/BottomSheet.cs
@@ -1,8 +1,6 @@
using System.Collections.ObjectModel;
using DIPS.Mobile.UI.Components.BottomSheets.Header;
using DIPS.Mobile.UI.Internal;
-using Colors = Microsoft.Maui.Graphics.Colors;
-using SearchBar = DIPS.Mobile.UI.Components.Searching.SearchBar;
namespace DIPS.Mobile.UI.Components.BottomSheets
{
@@ -19,9 +17,6 @@ public BottomSheet()
BottombarButtons = new ObservableCollection