16

I am a newbie to Xamarin.Forms Platform. I hope you can help me in moving ahead. I want to have a control like autocomplete in xamarin.forms like below

Autocomplete
(source: codetheory.in)

Can you please guide how can it be achievable in Xamarin.Forms? I want to achieve it with the Entry Control

TIA

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Manoj Sethi
  • 1,898
  • 8
  • 26
  • 56
  • 1
    Please next time write some code that you have tried or even some ideas that you have for what you want to achieve. – Imdad Jul 17 '16 at 14:22
  • Use my control its cross platform :)) https://github.com/cemozguraA/Xamarin.RisePlugin.AutoCompleteTextView – CemOzgurAa Jan 21 '20 at 08:18

7 Answers7

11

You haven't included what exactly you want, just some sort of autocomplete.

I'll bullet point the Manual way to do it in general for a List of items:

  1. Use a TextBox to allow the user to input text.
  2. Use a List to gather all your objects together with their searchable property such as the object Name.
  3. As the user types something in the TextBox, the app should search in the List for the String entered in the TextBox.
  4. The suggestions should be displayed, according to the String value typed, in a ListView under the TextBox.
  5. User clicks on the ListView item, which is a suggestion, and then that autocompletes by taking the object Name from the item clicked on, to the TextBox.

A general way of doing autocomplete without the long rough procedure above is to use the Android AutoCompleteTextView.

You can still use the basic logic to do it in Xamarin Forms.

Look here for the AutoCompleteTextView for Android. Look here, here and here for help with AutoComplete in Xamarin Forms.

Imdad
  • 769
  • 1
  • 11
  • 31
  • I have gone through AutoCompleteTextView but there is no provision of AutoComplete in Xamarin.iOS? Do you have any thoughts on this. – Manoj Sethi Jul 17 '16 at 15:00
  • @ManojSethi It's the same principle as described in the manual way of doing it. Those components are similarly available in Xamarin.iOS too. :) – Imdad Jul 19 '17 at 08:50
6

I have a Xamarin.Forms custom control you can use that works with iOS, Android and UWP. It uses custom renderers to provide native UI under the covers. I built this because I didn't find any control that provided a good native experience and didn't change the height of the control when the dropdown opens. All the doc + reference to the NuGet package is available here: https://github.com/dotMorten/XamarinFormsControls/tree/master/AutoSuggestBox

dotMorten
  • 1,953
  • 1
  • 14
  • 10
  • Nice plugin! But the iOS version looks kind of strange. :) – Genfood Jul 24 '18 at 13:50
  • There's been a lot of changes since, but feel free to log issues in the github repo with details on what you'd like to see, and we can take it from there. – dotMorten Jan 18 '19 at 23:58
2

I have implemented an AutocompleteView in my project. You can refer it.

public class AutoCompleteView : ContentView
{
    public static readonly BindableProperty SuggestionsProperty = BindableProperty.Create(nameof(Suggestions), typeof(IEnumerable), typeof(AutoCompleteView), null, BindingMode.OneWay, null, OnSuggestionsChanged);
    public static readonly BindableProperty SearchTextProperty = BindableProperty.Create(nameof(SearchText), typeof(string), typeof(AutoCompleteView), null, BindingMode.TwoWay, null, OnSearchTextChanged);
    public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(AutoCompleteView), null, BindingMode.OneWay, null, OnPlaceholderChanged);
    public static readonly BindableProperty MaximumVisibleSuggestionItemsProperty = BindableProperty.Create(nameof(MaximumVisibleSuggestionItems), typeof(int), typeof(AutoCompleteView), 4);
    public static readonly BindableProperty SuggestionItemTemplateProperty = BindableProperty.Create(nameof(SuggestionItemTemplate), typeof(DataTemplate), typeof(AutoCompleteView), null, BindingMode.OneWay, null, OnSuggestionItemTemplateChanged);
    public static readonly BindableProperty DisplayPropertyNameProperty = BindableProperty.Create(nameof(DisplayPropertyName), typeof(string), typeof(AutoCompleteView));

    public IEnumerable Suggestions
    {
        get { return (IEnumerable)GetValue(SuggestionsProperty); }
        set { SetValue(SuggestionsProperty, value); }
    }

    public string SearchText
    {
        get { return (string)GetValue(SearchTextProperty); }
        set { SetValue(SearchTextProperty, value); }
    }

    public string Placeholder
    {
        get { return (string)GetValue(PlaceholderProperty); }
        set { SetValue(PlaceholderProperty, value); }
    }

    public int MaximumVisibleSuggestionItems
    {
        get { return (int)GetValue(MaximumVisibleSuggestionItemsProperty); }
        set { SetValue(MaximumVisibleSuggestionItemsProperty, value); }
    }

    public DataTemplate SuggestionItemTemplate
    {
        get { return (DataTemplate)GetValue(SuggestionItemTemplateProperty); }
        set { SetValue(SuggestionItemTemplateProperty, value); }
    }

    public string DisplayPropertyName
    {
        get { return (string)GetValue(DisplayPropertyNameProperty); }
        set { SetValue(DisplayPropertyNameProperty, value); }
    }

    public ItemsStack SuggestionsListView { get; private set; }
    public Entry SearchEntry { get; private set; }
    public IEnumerable OriginSuggestions { get; private set; }
    public NestedScrollView SuggestionWrapper { get; private set; }
    public Grid Container { get; private set; }

    public bool IsSelected { get; private set; }
    public int TotalNumberOfTypings { get; private set; }

    private static void OnSuggestionsChanged(object bindable, object oldValue, object newValue)
    {
        var autoCompleteView = bindable as AutoCompleteView;

        var suggestions = (IEnumerable)newValue;
        autoCompleteView.OriginSuggestions = suggestions;

        suggestions = autoCompleteView.FilterSuggestions(suggestions, autoCompleteView.SearchText);
        autoCompleteView.SuggestionsListView.ItemsSource = suggestions;
    }

    private static void OnSearchTextChanged(object bindable, object oldValue, object newValue)
    {
        var autoCompleteView = bindable as AutoCompleteView;

        var suggestions = autoCompleteView.OriginSuggestions;
        if (newValue != null)
        {
            suggestions = autoCompleteView.FilterSuggestions(suggestions, autoCompleteView.SearchText);
            // assign when initializing with data
            if (autoCompleteView.SearchEntry.Text != autoCompleteView.SearchText)
            {
                autoCompleteView.SearchEntry.Text = autoCompleteView.SearchText;
            }
        }
        autoCompleteView.SuggestionsListView.ItemsSource = suggestions;

        if (Device.OS == TargetPlatform.Android)
        {
            // update the layout -> only do this when user is typing instead of selection an item from suggestions list 
            // -> prevent duplicated update layout
            if (!autoCompleteView.IsSelected)
            {
                autoCompleteView.UpdateLayout();
            }
            else
            {
                autoCompleteView.IsSelected = false;
            }
        }
    }

    private static void OnSuggestionItemTemplateChanged(object bindable, object oldValue, object newValue)
    {
        var autoCompleteView = bindable as AutoCompleteView;

        if (autoCompleteView.SuggestionsListView != null)
        {
            autoCompleteView.SuggestionsListView.ItemTemplate = autoCompleteView.SuggestionItemTemplate;
        }
    }

    public IEnumerable FilterSuggestions(IEnumerable suggestions, string keyword)
    {
        if (string.IsNullOrEmpty(keyword) || suggestions == null) return suggestions;

        var searchWords = keyword.ConvertToNonMark().ToLower().Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
        var result = suggestions.Cast<object>();
        foreach (var item in searchWords)
        {
            if (!string.IsNullOrEmpty(DisplayPropertyName))
            {
                result = result.Where(x => x.GetType().GetRuntimeProperty(DisplayPropertyName).GetValue(x).ToString().ConvertToNonMark().ToLower().Contains(item)).ToList();
            }
            else
            {
                result = result.Where(x => x.ToString().ConvertToNonMark().ToLower().Contains(item)).ToList();
            }
        }

        return result;
    }

    private static void OnPlaceholderChanged(object bindable, object oldValue, object newValue)
    {
        var autoCompleteView = bindable as AutoCompleteView;
        autoCompleteView.SearchEntry.Placeholder = newValue?.ToString();
    }

    public void UpdateLayout()
    {
        var expectedHeight = this.getExpectedHeight();
        Container.HeightRequest = expectedHeight;
        Container.ForceLayout();
    }

    private void SearchEntry_TextChanged(object sender, TextChangedEventArgs e)
    {
        TotalNumberOfTypings++;
        Device.StartTimer(TimeSpan.FromMilliseconds(1000), () => {
            TotalNumberOfTypings--;
            if (TotalNumberOfTypings == 0)
            {
                SearchText = e.NewTextValue;
            }
            return false;
        });
    }

    private void SearchEntry_Focused(object sender, FocusEventArgs e)
    {
        UpdateLayout();
        IsSelected = false;
    }

    private void SearchEntry_Unfocused(object sender, FocusEventArgs e)
    {
        Container.HeightRequest = 50;
        Container.ForceLayout();
    }

    private void SuggestionsListView_ItemSelected(object sender, ItemTappedEventArgs e)
    {
        IsSelected = true;
        SearchEntry.Text = !string.IsNullOrEmpty(DisplayPropertyName) ? e.Item?.GetType()?.GetRuntimeProperty(DisplayPropertyName)?.GetValue(e.Item)?.ToString() : e.Item?.ToString();
        Container.HeightRequest = 50;
        Container.ForceLayout();
    }

    private void OverlapContentView_Tapped(object sender, TappedEventArgs e)
    {
        UpdateLayout();
        IsSelected = false;

     }

    private int getExpectedHeight()
    {
        var items = SuggestionsListView.ItemsSource as IList;
        int wrapperHeightRequest = items != null ?
            (items.Count >= MaximumVisibleSuggestionItems ? MaximumVisibleSuggestionItems * 40 : items.Count * 40) : 0;
        if (Device.OS == TargetPlatform.Android)
        {
            return wrapperHeightRequest + 50;
        }
        return MaximumVisibleSuggestionItems * 40 + 50;
    }

    public AutoCompleteView()
    {
        Container = new Grid();
        SearchEntry = new Entry();
        SuggestionsListView = new ItemsStack();
        SuggestionWrapper = new NestedScrollView();

        // init Grid Layout
        Container.RowSpacing = 0;
        Container.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Star });
        Container.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Star });
        Container.RowDefinitions.Add(new RowDefinition() { Height = 50 });
        Container.HeightRequest = 50;

        // init Search Entry
        SearchEntry.HorizontalOptions = LayoutOptions.Fill;
        SearchEntry.VerticalOptions = LayoutOptions.Fill;
        SearchEntry.TextChanged += SearchEntry_TextChanged;
        SearchEntry.Unfocused += SearchEntry_Unfocused;
        SearchEntry.Focused += SearchEntry_Focused;

        // init Suggestions ListView
        SuggestionsListView.BackgroundColor = Color.White;
        SuggestionsListView.ItemTapped += SuggestionsListView_ItemSelected;
        SuggestionsListView.VerticalOptions = LayoutOptions.End;
        SuggestionsListView.Spacing = 1;

        // suggestions Listview's wrapper
        SuggestionWrapper.VerticalOptions = LayoutOptions.Fill;
        SuggestionWrapper.Orientation = ScrollOrientation.Vertical;
        SuggestionWrapper.BackgroundColor = Color.White;
        SuggestionWrapper.Content = SuggestionsListView;

        Container.Children.Add(SuggestionWrapper);
        Container.Children.Add(SearchEntry, 0, 1);

        this.Content = Container;
    }
}

Usage example:

<customControls:AutoCompleteView SearchText="{Binding User.UniversitySchool}" Suggestions="{Binding Schools}" DisplayPropertyName="Name" Placeholder="Please choose your school">
                    <customControls:AutoCompleteView.SuggestionItemTemplate>
                        <DataTemplate>
                            <ContentView Padding="10">
                                <Label Text="{Binding Name}" HeightRequest="20" LineBreakMode="HeadTruncation" Style="{StaticResource MainContentLabel}" />
                            </ContentView>
                        </DataTemplate>
                    </customControls:AutoCompleteView.SuggestionItemTemplate>
                </customControls:AutoCompleteView>

In this view, I used the ItemStack control. You can refer this: https://gist.github.com/NVentimiglia/2723411428cdbb72fac6

  • Where is NestedScrollView? It's only for Android or PCL? – Jesús Castro Sep 02 '17 at 15:36
  • Ah... You should create a custom view NestedScrollView, and renderer for it (Android only). Then, you set the native attribute NestedScrollingEnabled = true.(refer to https://developer.xamarin.com/api/property/Android.Views.View.NestedScrollingEnabled/). If you don't have this class, the AutoComplete's choices list can't be scrolled. – Quý Nguyễn Nam Sep 05 '17 at 03:14
  • @Quý Nguyễn Nam, do you have code for the NestedScrollView – Wes Feb 26 '18 at 18:54
  • @QuýNguyễnNam What is the string extension method "ConvertToNonMark" supposed to do? According to google, you are the only guy to ever have a function with this name! – Wes Feb 26 '18 at 19:16
  • @Wes "ConvertToNonMark" is my function (you can ignore it or need to define if your language includes mark (Eg: France, Vietnamese...) NestedScrollView is a custom XF control that extend from ScrollView. In Android (not need in iOS), you need to create a NestedScrollViewRenderer class that extend from ScrollViewRenderer, then set NestedScrollingEnabled = true. That's it! You can prefer to the link: https://developer.xamarin.com/api/property/Android.Views.View.NestedScrollingEnabled/ – Quý Nguyễn Nam Mar 02 '18 at 07:53
1

Please read these articles and try to implement solution on Xamarin.Forms using Custom Renderers.

Google Place API with Autocomplete in Xamarin Android

Xamarin.iOS Location Autocomplete by using Google Place API

Tomasz Kowalczyk
  • 1,873
  • 2
  • 23
  • 33
0

You can achieve this easily with SyncFusion AutoComplete plugin. This gives you various options to do rather than doing a custom render.

Reference: https://help.syncfusion.com/xamarin/sfautocomplete/getting-started

Selvarathinam Vinoch
  • 1,080
  • 3
  • 13
  • 23
0

I tried to build my own suggestions/autocomplete following the answer of Imdad. I was hindered by my my criteria where that it had to be shown on top or expand when suggestions filled the listview. Not have a listview taking up space permanently.

You can try https://github.com/XamFormsExtended/Xfx.Controls But I experienced some issues with it. It displays on top

I experienced an issue where text in autocompleteview doesn't update from source binding or set from code behind with https://github.com/XLabs/Xamarin-Forms-Labs autocompleteview. That pushes away what is in the way temporarily to display suggestions

I personally went this solution https://github.com/dotMorten/XamarinFormsControls/tree/master/AutoSuggestBox

lolelo
  • 698
  • 6
  • 18
0

I'm using this library SupportWidgetXF

It's cross platform.

Giuseppe Laera
  • 272
  • 2
  • 14