Step-by-Step Guide to Creating a Custom PhoneEntry with Country Code Dropdown in .NET MAUI
A country code dropdown is a crucial component in mobile apps, especially when paired with a custom phone entry field for a seamless user experience. In this article, we'll create a custom country code dropdown with a customized entry field in .NET MAUI.
Features of Our Custom Dropdown ๐ฏ
A custom design with Entry field displaying the country flag, code, and phone number input ๐ฑ
A dropdown (Modal Page) to select the country code ๐ฝ
A searchable country list for better usability ๐
Data binding using Bindable Properties ๐
Custom handlers to remove the entry border for Andorid, IOS, Mac and Windows ๐จ
Step 1: Write a custom handler to remove the border from the entry ๐
To ensure a clean design, we remove the default border from the Entry field using platform-specific handlers.
Android, IOS, Mac, Windows Entry Handler (MauiProgram.cs) ๐ค๐ฑ๐ฅ๏ธ๐ช
// Custom mapper for borderless entry
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping(nameof(Entry), (handler, view) =>
{
#if IOS || MACCATALYST
handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear;
handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.None;
#elif ANDROID
handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Microsoft.Maui.Graphics.Colors.Transparent.ToAndroid());
#elif WINDOWS
handler.PlatformView.BorderThickness = new Microsoft.UI.Xaml.Thickness(0);
handler.PlatformView.Background = null;
#endif
});
Step 2: Create the Custom Control for PhoneEntry โ๏ธ
In this step, you will create a custom control for the phone entry field. This control will display the country flag, code, and provide an input for the phone number, enhancing the user interface and experience.
CustomPhoneEntry.Xaml ๐ฑ
<Grid xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PhoneEntryControl.Controls.CustomPhoneEntry"
xmlns:ios="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls"
xmlns:ff="clr-namespace:FFImageLoading.Svg.Maui;assembly=FFImageLoading.Compat.Svg.Maui"
RowDefinitions="Auto"
ColumnDefinitions="Auto,*"
x:Name="this">
<Border Grid.Column="0" Stroke="#9CA0B1" StrokeShape="RoundRectangle 7" Padding="10,3">
<HorizontalStackLayout BindingContext="{Binding SelectedCountry, Source={x:Reference this}}" Spacing="10" VerticalOptions="Center" HorizontalOptions="Fill">
<!-- Flag -->
<ff:SvgCachedImage x:Name="imgFlag" Source="{Binding Image}"
HeightRequest="25" WidthRequest="40" Aspect="AspectFit" LoadingPlaceholder="imgloading.gif" DownsampleToViewSize="True"/>
<!-- Country Name -->
<Label x:Name="lblName" Text="{Binding Value}" VerticalOptions="Center"/>
<!-- DD Arrow -->
<Image Source="black_dropdown" Opacity=".5" HeightRequest="15" WidthRequest="15" VerticalOptions="Center"/>
</HorizontalStackLayout>
<Border.GestureRecognizers>
<TapGestureRecognizer Tapped="OpenCountiesCodeModel"/>
</Border.GestureRecognizers>
</Border>
<Border Grid.Column="1" Stroke="#9CA0B1" StrokeShape="RoundRectangle 7" Padding="10,3">
<Entry
x:Name="txtEntry"
Grid.Column="0"
ios:Entry.CursorColor="Black"
IsSpellCheckEnabled="False"
ClearButtonVisibility="WhileEditing"
IsTextPredictionEnabled="False"
Keyboard="{Binding Source={x:Reference this},Path= Keyboard}"
Text="{Binding Source={x:Reference this},Path= Text,Mode=TwoWay}"
</Entry.Behaviors>
</Entry>
</Border>
</Grid>
Now we need to create a bindable property for the list of countries that we will pass to generic dropdown page and selected country to display on the phone entry.
CustomPhoneEntry.cs
public partial class CustomPhoneEntry: Grid
{
public CustomPhoneEntry()
{
try
{
InitializeComponent();
}
catch { }
}
public static readonly BindableProperty SelectedCountryProperty =
BindableProperty.Create(nameof(SelectedCountry), typeof(Generic), typeof(CustomPhoneEntry), null, BindingMode.TwoWay);
public Generic SelectedCountry
{
get => (Generic)GetValue(SelectedCountryProperty);
set { SetValue(SelectedCountryProperty, value); }
}
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create(nameof(ItemsSource), typeof(ObservableCollection<Generic>), typeof(CustomPhoneEntry), new ObservableCollection<Generic>(), BindingMode.TwoWay);
public ObservableCollection<Generic> ItemsSource
{
get => (ObservableCollection<Generic>)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public static readonly BindableProperty TextProperty = BindableProperty.Create(
propertyName: nameof(Text),
returnType: typeof(string),
declaringType: typeof(CustomPhoneEntry),
defaultValue: null,
defaultBindingMode: BindingMode.TwoWay);
public string Text
{
get => (string)GetValue(TextProperty);
set { SetValue(TextProperty, value); }
}
private async Task OpenModal()
{
// Push the modal for selecting a country
var countrySelectionPage = new GenericDropdownPage(ItemsSource, SelectedCountry,"Search by code");
countrySelectionPage.CountrySelected += (s, selectedCountry) =>
{
SelectedCountry = selectedCountry;
};
await AppShell.Current.Navigation.PushModalAsync(countrySelectionPage);
}
async void OpenCountiesCodeModel(System.Object sender, Microsoft.Maui.Controls.TappedEventArgs e)
{
await OpenModal();
}
}
Step 3: Create the Custom Dropdown (Modal Page) ๐
A modal page can be used to display the country list:
GenericDropdownPage.Xaml
ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PhoneEntryControl.Controls.GenericDropdownPage"
xmlns:ff="clr-namespace:FFImageLoading.Svg.Maui;assembly=FFImageLoading.Compat.Svg.Maui"
Title="CountryCodeDropdown">
<Grid RowDefinitions="Auto,*" Padding="15" RowSpacing="15">
<!-- Search Bar -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,*" ColumnSpacing="0">
<ImageButton Grid.Column="0" Source="left_arrow" Clicked="OnBackPressed"/>
<Border Grid.Column="1" BackgroundColor="#F0F0F0" Stroke="#E0E0E0" StrokeThickness="1" Style="{StaticResource RoundRecBorder}" Padding="5">
<Grid ColumnDefinitions="Auto,*" ColumnSpacing="10" VerticalOptions="Center">
<Image Source="search"
Grid.Column="0"
Margin="10,0,0,0"
WidthRequest="20"
HeightRequest="20"
VerticalOptions="Center" />
<Entry x:Name="txtSearch"
Placeholder="Search by name or code"
Grid.Column="1"
FontSize="16"
Text="{Binding SearchQuery, Mode=TwoWay}"
VerticalOptions="Center"
ClearButtonVisibility="WhileEditing"
TextChanged="OnSearchTextChanged"/>
</Grid>
</Border>
</Grid>
<!-- Dropdown Menu -->
<CollectionView x:Name="dropdownMenu"
Grid.Row="1"
ItemsSource="{Binding FilteredItemsSource}"
SelectionMode="Single"
SelectionChanged="OnSelectionChanged"
VerticalOptions="FillAndExpand">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" ItemSpacing="5"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource RoundRecBorder}">
<HorizontalStackLayout Spacing="10" VerticalOptions="Center" Padding="10">
<!-- Country Flag -->
<ff:SvgCachedImage Source="{Binding Image}"
WidthRequest="40"
HeightRequest="25"
Aspect="AspectFit"
LoadingPlaceholder="imgloading.gif" DownsampleToViewSize="True" />
<!-- Country Details -->
<Label Text="{Binding Value}" Style="{StaticResource SmallMediumHorizontalStart}" VerticalOptions="Center"/>
</HorizontalStackLayout>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</ContentPage>
GenericDropdownPage.cs
public partial class GenericDropdownPage : ContentPage
{
public event EventHandler<Generic> CountrySelected;
public ObservableCollection<Generic> ItemsSource { get; set; }
public ObservableCollection<Generic> _cachedList;
public string SearchQuery { get; set; }
public GenericDropdownPage(ObservableCollection<Generic> itemsSource, Generic selectedCountry, string placeholder)
{
InitializeComponent();
txtSearch.Placeholder = placeholder;
ItemsSource = itemsSource;
dropdownMenu.ItemsSource = new ObservableCollection<Generic>(itemsSource);
_cachedList= new ObservableCollection<Generic>(itemsSource);
BindingContext = this;
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection.FirstOrDefault() is Generic selectedCountry)
{
CountrySelected?.Invoke(this, selectedCountry);
Navigation.PopModalAsync();
}
}
private void OnSearchTextChanged(object sender, TextChangedEventArgs e)
{
var query = e.NewTextValue?.ToLowerInvariant();
if (string.IsNullOrWhiteSpace(query))
{
// Reset to original list
dropdownMenu.ItemsSource = new ObservableCollection<Generic>(_cachedList);
}
else
{
// Filter from the master list (_allItems) instead of already filtered data
dropdownMenu.ItemsSource = new ObservableCollection<Generic>(
_cachedList.Where(c => c.Value.ToLowerInvariant().Contains(query))
);
}
}
private async void OnBackPressed(System.Object sender, System.EventArgs e)
{
await AppShell.Current.Navigation.PopModalAsync();
}
}
Finally, we are done with our dropdown. It's time to add it to a page and make it work .๐ฅ
Finally, integrate the newly built control into any design and enjoy!
CreateAccountPage.Xaml
ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PhoneEntryControl.Views.Registeration.CreateAccountPage"
xmlns:customEntry="clr-namespace:PhoneEntryControl.Controls"
Title="CountryCodeDropdown">
<VerticalStackLayout>
<customEntry:CustomPhoneEntry Text="{Binding MobileNumber}" ItemsSource="{Binding CountriesList}" SelectedCountry="{Binding SelectedCountry}"/>
</VerticalStackLayout>
</ContentPage>
CreateAccountPage.cs
public partial class CreateAccountPage : ContentPage
{
public CreateAccountPage(CreateAccountViewModel createAccountViewModel)
{
try
{
InitializeComponent();
BindingContext = createAccountViewModel;
}
catch(Exception ex) { }
}
}
CreateAccountViewModel.cs
public class CreateAccountViewModel : ObservableObject
{
#region Properties
[ObservableProperty]
public Generic _selectedCountry;
[ObservableProperty]
public ObservableCollection<Generic> _countriesList;
[ObservableProperty]
public string _mobileNumber;
#endregion
public CountryPickerViewModel()
{
CountriesList = new ObservableCollection<Country>
{
new Country { Name = "Pakistan", Code = "+92", Flag = "pk" },
new Country { Name = "Saudia Arabia", Code = "+966", Flag = "sa" },
new Country { Name = "Turkey", Code = "+90", Flag = "tr" },
};
SelectedCountry = Countries.First();
}
}
Conclusion ๐ฏ
By implementing a custom country code dropdown with an entry field, we enhance the user experience and provide a more intuitive phone number input system. This approach can be further customized based on specific app requirements.
Stay tuned for more awesome .NET MAUI tutorials.
Happy coding! ๐๐ฅ