> ## Content Index
> Fetch the complete content index at: https://codetraveler.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# Adding a Search Bar to Xamarin.Forms.NavigationPage
- URL: https://codetraveler.io/2020/10/31/adding-a-search-bar-to-xamarin-forms-navigationpage/
- Published: 2020-10-31T18:19:00.000Z
- Updated: 2023-08-11T17:08:55.000Z
- Author: Brandon Minnick
- Tags: Xamarin.Forms, Android, C#, iOS, mobile, microsoft, .NET, Xamarin, Xamarin.Android, Xamarin.iOS, NavigationPage, NavigationBar, Search, Search Bar, SearchBar, Android.Support.V7.Widget.SearchView, UISearchController, Xamarin.Forms.ContentPage, ContentPage, Large Titles, CustomRenderer, Custom Renderer, Custom Renderers, Xamarin.Forms.NavigationPage, UINavigationController, UIViewController

**31 Oct 2020 Update:** Updated for AndroidX + Xamarin.Essentials

Let's look at how to add the native search bar control into the `Xamarin.Forms.NavigationPage`

Both Xamarin.iOS and Xamarin.Android offer the capability to natively add a search bar to the navigation bar using `UISearchController` and `SearchView`, respectively. 

But, it's not simple in Xamarin.Forms as one might expect.

If you'd like to see a completed solution, check out this app: [](https://github.com/brminnick/GitHubXamarin?ref=codetraveler.io)[https://github.com/brminnick/GitTrends](https://github.com/brminnick/GitTrends?ref=codetraveler.io)

Otherwise, let's jump to the steps & code following this lovely GIF:

| iOS                                                                                                              | Android                                                                                                              |
| ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| ![iOS Gif](https://user-images.githubusercontent.com/13558917/62826588-d6c73480-bb83-11e9-8536-ec67e32c5c82.gif) | ![Android Gif](https://user-images.githubusercontent.com/13558917/62826859-e432ed80-bb88-11e9-8418-3ce1cde2dd42.gif) |

## Xamarin.Forms Project

In the Xamarin.Forms project, we'll want to do the following:

- Use a [Xamarin.Forms Platform Specific](https://docs.microsoft.com/dotnet/api/xamarin.forms.platformconfiguration.iosspecific.navigationpage.setpreferslargetitles?WT.mc%5Fid=navigationbar-codetraveler-bramin&ref=codetraveler.io) to use Large Titles on iOS
- Create an interface, `ISearchPage`
- In our `Xamarin.Forms.ContentPage`, implement `ISearchPage`

### iOS Large Titles

In iOS 11, Apple introduced [prefersLargeTitles](https://developer.apple.com/documentation/uikit/uinavigationbar/2908999-preferslargetitles?ref=codetraveler.io) and updated their [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/ios/bars/navigation-bars/?ref=codetraveler.io), recommending that iOS apps use Large Titles.

This is easy to accomplish in Xamarin.Forms thanks to the platform-specific method `[Xamarin.Forms.PlatformConfiguration.iOSSpecific.NavigationPage.SetPreferesLargeTitles](https://docs.microsoft.com/dotnet/api/xamarin.forms.platformconfiguration.iosspecific?view=xamarin-forms?WT.mc%5Fid=navigationbar-codetraveler-bramin&ref=codetraveler.io)` .

We can set this platform-specific in our `App.cs` class:

```csharp
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;

public class App : Xamarin.Forms.Application
{
    public App()
    {
        var navigationPage = new Xamarin.Forms.NavigationPage(new MyContentPage());

        navigationPage.On<iOS>().SetPrefersLargeTitles(true);

        MainPage = navigationPage;
    }
}
```

### ISearchPage

Let's create an interface, `ISearchPage`, that can be used across the Xamarin.Forms, Xamarin.Android and Xamarin.iOS projects.

```csharp
public interface ISearchPage
{
    void OnSearchBarTextChanged(string text);
    event EventHandler<string> SearchBarTextChanged;
}
```

### Xamarin.Forms.ContentPage

Let's now implement `ISearchPage` in our `Xamarin.Forms.ContentPage`

```csharp
public class MyContentPage : ContentPage, ISearchPage
{
    public MyContentPage()
    {
        SearchBarTextChanged += HandleSearchBarTextChanged;
    }

    public event EventHandler<string> SearchBarTextChanged;

    void ISearchPage.OnSearchBarTextChanged((string text) => SearchBarTextChanged?.Invoke(this, text);

    void HandleSearchBarTextChanged(object sender, string searchBarText)
    {
        //Logic to handle updated search bar text
    }     
}
```

## Xamarin.iOS Project

In the Xamarin.iOS project, we'll be doing the following:

- Create `SearchPageRenderer`

Our Xamarin.iOS [Custom Renderer](https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/custom-renderer?WT.mc%5Fid=navigationbar-codetraveler-bramin&ref=codetraveler.io) uses a `PageRenderer` that adds a `UISearchController` to the `NavigationItem` on its parent page which is a `UINavigationController`. (The parent page is a `UINavigationController`, because in `App.cs`, we used a `Xamarin.Forms.NavigationPage`.)

If you're new to Xamarin.iOS and/or Custom Renderers, here's a chart showing how the various UI controls relate to each other on iOS:

| Xamarin.iOS            | Xamarin.Forms Renderer | Xamarin.Forms  |
| ---------------------- | ---------------------- | -------------- |
| UINavigationController | NavigationRenderer     | NavigationPage |
| UIViewController       | PageRenderer           | ContentPage    |

```csharp
using System;
using UIKit;
using MyNamespace;
using MyNamespace.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(MyContentPage), typeof(SearchPageRenderer))]
namespace MyNamespace.iOS
{
    public class SearchPageRenderer : PageRenderer, IUISearchResultsUpdating
    {
		readonly UISearchController _searchController;

        public SearchPageRenderer()
        {
            _searchController = new UISearchController(searchResultsController: null)
            {
                SearchResultsUpdater = this,
                DimsBackgroundDuringPresentation = false,
                HidesNavigationBarDuringPresentation = false,
                HidesBottomBarWhenPushed = true
            };
            _searchController.SearchBar.Placeholder = string.Empty;
        }

        public override void ViewDidAppear(bool animated)
        {
            base.ViewDidAppear(animated);

            if (ParentViewController.NavigationItem.SearchController is null)
            {
                ParentViewController.NavigationItem.SearchController = _searchController;
                DefinesPresentationContext = true;

                //Work-around to ensure the SearchController appears when the page first appears https://stackoverflow.com/a/46313164/5953643
                ParentViewController.NavigationItem.SearchController.Active = true;
                ParentViewController.NavigationItem.SearchController.Active = false;
            }
        }

        public override void ViewWillDisappear(bool animated)
        {
            base.ViewWillDisappear(animated);

            ParentViewController.NavigationItem.SearchController = null;
        }

        public void UpdateSearchResultsForSearchController(UISearchController searchController)
        {
            if (Element is ISearchPage searchPage)
                searchPage.OnSearchBarTextChanged(searchController.SearchBar.Text);
        }
        
        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);

            if (e.NewElement != null)
                e.NewElement.SizeChanged += HandleSizeChanged;
        }
        
        //Work-around to accommodate UISearchController height, https://github.com/brminnick/GitTrends/issues/171
        void HandleSizeChanged(object sender, EventArgs e)
        {
            if (ParentViewController?.NavigationItem.SearchController != null
                && Element.Height > -1
                && Element is Page page)
            {
                Element.SizeChanged -= HandleSizeChanged;

                if (NavigationController.NavigationBar.PrefersLargeTitles is true)
                {
                    var statusBarSize = UIApplication.SharedApplication.StatusBarFrame.Size;
                    var statusBarHeight = Math.Min(statusBarSize.Height, statusBarSize.Width);

                    page.Padding = new Thickness(page.Padding.Left,
                                                    page.Padding.Top,
                                                    page.Padding.Right,
                                                    page.Padding.Bottom + statusBarHeight);
                }
            }
        }
    }
}
```

**Note**: If you'd like to add a search bar to multiple pages, you can add additional `ExportRenderer` assembly attributes like so:

```csharp
[assembly: ExportRenderer(typeof(MyContentPage), typeof(SearchPageRenderer))]
[assembly: ExportRenderer(typeof(MyContentPage2), typeof(SearchPageRenderer))]
```

## Xamarin.Android Project

In the Xamarin.Android project, we'll be doing the following:

- Create `Resources > menu > MainMenu.xml`
- Create `SearchPageRenderer`

### MainMenu.xml

In the Xamarin.Android project, we'll first need to add `MainMenu.xml` to `Resources > menu`.

1\. In the Xamarin.Android project, in the `Resources` folder, create a new folder called `menu` (if one doesn't already exist)

- **Note**: The folder name, `menu`, needs to be lowercase

2\. In the `Resources > menu` folder, create a new file called `MainMenu.xml`

![](https://storage.ghost.io/c/dc/c2/dcc2e43e-24f8-46bb-a899-9c446f3ef794/content/images/2019/08/Screen-Shot-2019-08-10-at-12.30.58-PM.png)

3\. Open `Resources > menu > MainMenu.xml`

4\. In `MainMenu.xml`, add the following code:

```xml
<?xml version="1.0" encoding="utf-8" ?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
  <item android:id="@+id/ActionSearch"
        android:title="Filter"
        android:icon="@android:drawable/ic_menu_search"
        app:showAsAction="always|collapseActionView"
        app:actionViewClass="androidx.appcompat.widget.SearchView"/>
</menu>
```

### Xamarin.Android Custom Renderer

Our Xamarin.Android [Custom Renderer](https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/custom-renderer?WT.mc%5Fid=navigationbar-codetraveler-bramin&ref=codetraveler.io) uses a `PageRenderer` to add a `SearchView` to the `Toolbar`.

- **Note:** Our custom renderer requires the [Xamarin.Essentials NuGet Package](https://www.nuget.org/packages/Xamarin.Essentials/?ref=codetraveler.io). Make sure to add the NuGet package to your Xamarin.Android project first and follow [its instructions to initialize it](https://docs.microsoft.com/xamarin/essentials/get-started?tabs=macos%2Candroid&WT.mc%5Fid=mobile-0000-bramin&ref=codetraveler.io) in the `MainApplication`.

```csharp
using System.Collections.Generic;
using System.Linq;
using Android.Content;
using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using AndroidX.AppCompat.Widget;
using MyNamespace;
using MyNamespace.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(MyContentPage), typeof(SearchPageRenderer))]
namespace MyNamespace.Droid
{
    public class SearchPageRenderer : PageRenderer
    {
        public SearchPageRenderer(Context context) : base(context)
        {

        }
        
        //Add the Searchbar once Xamarin.Forms creates the Page
        protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement is ISearchPage && e.NewElement is Page page && page.Parent is NavigationPage navigationPage && navigationPage.CurrentPage is ISearchPage)
                AddSearchToToolbar(page.Title);
        }

        protected override void OnAttachedToWindow()
        {
            base.OnAttachedToWindow();

			if (Element is ISearchPage && Element is Page page && page.Parent is NavigationPage navigationPage)
            {
            	//Workaround to re-add the SearchView when navigating back to an ISearchPage, because Xamarin.Forms automatically removes it
                navigationPage.Popped += HandleNavigationPagePopped;
                navigationPage.PoppedToRoot += HandleNavigationPagePopped;
            }
        }
        
        //Adding the SearchBar in OnSizeChanged ensures the SearchBar is re-added after the device is rotated, because Xamarin.Forms automatically removes it
        protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
        {
            base.OnSizeChanged(w, h, oldw, oldh);

            if (Element is ISearchPage && Element is Page page && page.Parent is NavigationPage navigationPage && navigationPage.CurrentPage is ISearchPage)
            {
                AddSearchToToolbar(page.Title);
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (GetToolbar() is Toolbar toolBar)
                toolBar.Menu?.RemoveItem(Resource.Menu.MainMenu);

            base.Dispose(disposing);
        }
        
        static IEnumerable<Toolbar> GetToolbars(ViewGroup viewGroup)
        {
            for (int i = 0; i < viewGroup.ChildCount; i++)
            {
                if (viewGroup.GetChildAt(i) is Toolbar toolbar)
                {
                    yield return toolbar;
                }
                else if (viewGroup.GetChildAt(i) is ViewGroup childViewGroup)
                {
                    foreach (var childToolbar in GetToolbars(childViewGroup))
                        yield return childToolbar;
                }
            }
        }

        Toolbar? GetToolbar()
        {
            if (Xamarin.Essentials.Platform.CurrentActivity.Window?.DecorView.RootView is ViewGroup viewGroup)
            {
                var toolbars = GetToolbars(viewGroup);

                //Return top-most Toolbar
                return toolbars.LastOrDefault();
            }

            return null;
        }
        
        //Workaround to re-add the SearchView when navigating back to an ISearchPage, because Xamarin.Forms automatically removes it
        void HandleNavigationPagePopped(object sender, NavigationEventArgs e)
        {
            if (sender is NavigationPage navigationPage
                && navigationPage.CurrentPage is ISearchPage)
            {
                AddSearchToToolbar(navigationPage.CurrentPage.Title);
            }
        }

        void AddSearchToToolbar(string pageTitle)
        {
            if (GetToolbar() is Toolbar toolBar
                && toolBar.Menu?.FindItem(Resource.Id.ActionSearch)?.ActionView?.JavaCast<SearchView>()?.GetType() != typeof(SearchView))
            {
                toolBar.Title = pageTitle;
                toolBar.InflateMenu(Resource.Menu.MainMenu);

                if (toolBar.Menu?.FindItem(Resource.Id.ActionSearch)?.ActionView?.JavaCast<SearchView>() is SearchView searchView)
                {
                    searchView.QueryTextChange += HandleQueryTextChange;
                    searchView.ImeOptions = (int)ImeAction.Search;
                    searchView.InputType = (int)InputTypes.TextVariationFilter;
                    searchView.MaxWidth = int.MaxValue; //Set to full width - http://stackoverflow.com/questions/31456102/searchview-doesnt-expand-full-width
                }
            }
        }

        void HandleQueryTextChange(object sender, SearchView.QueryTextChangeEventArgs e)
        {
            if (Element is ISearchPage searchPage)
                searchPage.OnSearchBarTextChanged(e?.NewText ?? string.Empty);
        }
    }
}
```

**Note**: If you'd like to add a search bar to multiple pages, you can add additional `ExportRenderer` assembly attributes like so:

```csharp
[assembly: ExportRenderer(typeof(MyContentPage), typeof(SearchPageRenderer))]
[assembly: ExportRenderer(typeof(MyContentPage2), typeof(SearchPageRenderer))]
```