Picker does not gain focus on iOS

Maximilian Grabner 5 Reputation points
2024-08-08T11:50:53.3066667+00:00

Hi
I am currently migrating our Xamarin app to MAUI. But there are multiple problems, we face time after time. The most we can solve by some workarounds. But this time, I don´t know if there is a workaround.
The problem is, when tapping on our picker, on iOS it does not gain focus (Android works fine). So i can´t select one of the items.

My xaml-code:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
			 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
			 xmlns:local="clr-namespace:Vogel.MAUI.App.Base"
			 x:Class="Vogel.MAUI.App.Base.Controls.Pickers.RoundedPicker"
			 x:Name="Root">

	<Grid HorizontalOptions="Fill"
		  HeightRequest="{ OnPlatform iOS=44, Android=50 }"
		  MinimumHeightRequest="{ OnPlatform iOS=44, Android=50 }"
		  ColumnSpacing="0">

		<Grid.ColumnDefinitions>
			<ColumnDefinition Width="*" />
			<ColumnDefinition Width="Auto" />
			<ColumnDefinition Width="40" />
		</Grid.ColumnDefinitions>

		<!-- The Frame element prevents the functionality of the GestureRecognizer binded on the grid. -->
		<!-- GestureRecognizer was transfered to the Frame element.  -->
		
		<!-- Border -->
		<Frame Grid.Column="0"
			   Grid.ColumnSpan="3"
			   Margin="0"
			   Padding="0"
			   CornerRadius="{ OnPlatform iOS=22, Android=25 }"
			   BorderColor="{ Binding BorderColor, Source={ x:Reference Root } }"
			   BackgroundColor="White"
			   HasShadow="False">
			<Frame.GestureRecognizers>
				<TapGestureRecognizer Tapped="OnRoundedPickerTapped" />
			</Frame.GestureRecognizers>
		</Frame>

		<!-- Placeholder -->
		<Label Grid.Column="0"
			   VerticalOptions="Center"
			   HorizontalOptions="Fill"
			   HorizontalTextAlignment="{ DynamicResource LayoutDirectionHorizontalTextAlignment }"
			   Padding="20,0,20,0"
			   TextColor="#CCCCCC"
			   LineHeight="{ OnPlatform iOS=1, Android=0.9 }"
			   FontSize="15"
			   FontFamily="{ StaticResource AppSemiboldFontFamily }"
			   BindingContext="{ x:Reference Name=picker }"
			   Text="{ Binding Placeholder, Source={x:Reference Root} }"
			   IsVisible="{ Binding PlaceholderVisible, Source={x:Reference Root} }" />

		<!-- SelectedItem -->
		<Label Grid.Column="0"
			   VerticalOptions="Center"
			   HorizontalOptions="Fill"
			   HorizontalTextAlignment="{ DynamicResource LayoutDirectionHorizontalTextAlignment }"
			   Padding="20,0,20,0"
			   TextColor="Black"
			   LineHeight="{ OnPlatform iOS=1, Android=0.9 }"
			   FontSize="15"
			   FontFamily="{ StaticResource AppSemiboldFontFamily }"
			   BindingContext="{ x:Reference Name=picker }"
			   Text="{ Binding Path=SelectedItem }" />

		<!-- ResetButton -->
		<Label Grid.Column="1"
			   VerticalOptions="Fill"
			   VerticalTextAlignment="Center"
			   HorizontalOptions="Fill"
			   HorizontalTextAlignment="Center"
			   TextColor="Black"
			   WidthRequest="38"
			   MinimumWidthRequest="38"
			   FontSize="22"
			   FontFamily="{ StaticResource GrialIconsFill }"
			   Text="{ x:Static local:GrialIconsFont.Close }"
			   IsVisible="{ Binding ResetButtonVisible, Source={x:Reference Root} }">
			<Label.GestureRecognizers>
				<TapGestureRecognizer Tapped="OnResetButtonClicked" />
			</Label.GestureRecognizers>
		</Label>

		<!-- ChevronDown (Default) -->
		<Label Grid.Column="2"
			   VerticalOptions="Center"
			   HorizontalOptions="Fill"
			   HorizontalTextAlignment="{ DynamicResource LayoutDirectionHorizontalTextAlignment}"
			   Padding="0,2,0,0"
			   TextColor="Black"
			   FontSize="26"
			   FontFamily="{ StaticResource GrialIconsFill }"
			   Text="{ Binding IconPicker, Source={ x:Reference Root } }" />

		<!-- Picker -->
		<Picker x:Name="picker"
				Grid.Column="0"
				Grid.ColumnSpan="3"
				FontSize="0"
				TextColor="Transparent"
				InputTransparent="True"
				BackgroundColor="Transparent"
				ItemsSource="{ Binding Items, Source={ x:Reference Root } }"
				ItemDisplayBinding="{ Binding DisplayText }"
				SelectedItem="{ Binding SelectedItem, Source={ x:Reference Root } }" />
	</Grid>
</ContentView>

My code behind:

using NLog;
using System.Collections.ObjectModel;

namespace Vogel.MAUI.App.Base.Controls.Pickers;

public partial class RoundedPicker : ContentView
{
	private static readonly ILogger _NLog = LogManager.GetCurrentClassLogger();

	public RoundedPicker()
	{
		Items = new ObservableCollection<RoundedPickerItem>();
		InitializeComponent();
	}

	public static readonly BindableProperty ItemsProperty =
	   BindableProperty.Create(
		   nameof(Items),
		   typeof(ObservableCollection<RoundedPickerItem>),
		   typeof(RoundedPicker));

	public ObservableCollection<RoundedPickerItem> Items
	{
		get { return (ObservableCollection<RoundedPickerItem>)GetValue(ItemsProperty); }
		set { SetValue(ItemsProperty, value); }
	}

	public static readonly BindableProperty IconPickerProperty =
	   BindableProperty.Create(
		   nameof(IconPicker),
		   typeof(string),
		   typeof(RoundedPicker),
		   defaultValue: "");

	public string IconPicker
	{
		get { return (string)GetValue(IconPickerProperty); }
		set { SetValue(IconPickerProperty, value); }
	}

	public static readonly BindableProperty ShowResetButtonProperty =
	   BindableProperty.Create(
		   nameof(ShowResetButton),
		   typeof(bool),
		   typeof(RoundedPicker),
		   defaultValue: true);

	public bool ShowResetButton
	{
		get { return (bool)GetValue(ShowResetButtonProperty); }
		set { SetValue(ShowResetButtonProperty, value); }
	}

	public static readonly BindableProperty SelectedItemProperty =
	  BindableProperty.Create(
		  nameof(SelectedItem),
		  typeof(RoundedPickerItem),
		  typeof(RoundedPicker),
		  defaultValue: null,
		  propertyChanged: OnIsSelectedChanged);

	public RoundedPickerItem SelectedItem
	{
		get { return (RoundedPickerItem)GetValue(SelectedItemProperty); }
		set { SetValue(SelectedItemProperty, value); }
	}

	public static readonly BindableProperty PlaceholderProperty =
	  BindableProperty.Create(
		  nameof(Placeholder),
		  typeof(string),
		  typeof(RoundedPicker),
		  defaultValue: "");

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

	public bool PlaceholderVisible => SelectedItem == null;
	public bool ResetButtonVisible => ShowResetButton && SelectedItem != null;

	private static void OnIsSelectedChanged(BindableObject bindable, object oldValue, object newValue)
	{
		RoundedPicker picker = bindable as RoundedPicker;
		picker.OnPropertyChanged(nameof(PlaceholderVisible));
		picker.OnPropertyChanged(nameof(ResetButtonVisible));
	}

	public static readonly BindableProperty BorderColorProperty =
	   BindableProperty.Create(
		   nameof(BorderColor),
		   typeof(Color),
		   typeof(RoundedPicker),
		   defaultValue: Colors.Black);

	public Color BorderColor
	{
		get { return (Color)GetValue(BorderColorProperty); }
		set { SetValue(BorderColorProperty, value); }
	}

	private bool _RoundedPickerTappedLock = false; // prevent double tapping

	protected async void OnRoundedPickerTapped(object sender, EventArgs e)
	{
		if (_RoundedPickerTappedLock)
		{
			_NLog?.Debug("OnRoundedPickerTapped double-tapping prevented!");
			return;
		}
		_RoundedPickerTappedLock = true;
		try
		{
			// Workaround for android(API 28+) because already focused picker focus don't trigger picker selection popup
			if (DeviceInfo.Current.Platform == DevicePlatform.Android && picker.IsFocused)
				picker.Unfocus();

			// Workaround for ios because click on right end of picker does not trigger
			picker.Focus();

			// Lock it longer for extra double tapping prevention
			await Task.Delay(400);
		}
		catch (ObjectDisposedException)
		{
			_NLog?.Debug("OnRoundedPickerTapped: Picker object already disposed");
		}
		catch (Exception ex)
		{
			_NLog?.Error(ex, "OnRoundedPickerTapped: Unhandled exception");
		}
		finally
		{
			_RoundedPickerTappedLock = false;
		}
	}

	private void OnResetButtonClicked(object sender, EventArgs args)
	{
		SelectedItem = null;
	}

	public class RoundedPickerItem : ObservableObject
	{
		public RoundedPickerItem() : base(false) { }
		public RoundedPickerItem(string key, string text) : this()
		{
			_Key = key;
			_Text = text;
		}

		private string _Key = null;
		public virtual string Key
		{
			get => _Key;
			set
			{
				if (_Key != value)
				{
					_Key = value;
					NotifyPropertyChanged("Key");
				}
			}
		}

		private string _Text = null;
		public virtual string Text
		{
			get => _Text;
			set
			{
				if (_Text != value)
				{
					_Text = value;
					NotifyPropertyChanged("Text");
				}
			}
		}

		private string _DisplayText = null;
		public virtual string DisplayText
		{
			get => _DisplayText ?? Text;
			set
			{
				if (_DisplayText != value)
				{
					_DisplayText = value;
					NotifyPropertyChanged("DisplayText");
				}
			}
		}

		public override string ToString()
		{
			return Text;
		}
	}
}

In my opinion the only relevant part in the code behind is in the "OnRoundedPickerTapped". For Xamarin we added the "picker.Focus()" to set the focus on the picker (we left some comments to explain why we did some parts). But now on MAUI it seems that this does not work anymore.

As already said on Android it works completely fine, only iOS leads to problems.

Maybe some of you guys can help me/us to solve this problem.

.NET MAUI
.NET MAUI
A Microsoft open-source framework for building native device applications spanning mobile, tablet, and desktop.
3,594 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Wenyan Zhang (Shanghai Wicresoft Co,.Ltd.) 32,386 Reputation points Microsoft Vendor
    2024-08-29T08:21:41.6533333+00:00

    Hello,

    I think I found the problem. On iOS the property ´inputTransparent="True"´ does not work, when there is a backgroundColor or textColor set to transparent.

    Yes, I reproduce the issue. Please report this issue at GitHub- https://github.com/dotnet/maui/issues/new/choose, and wait for their updating, thanks for your collaboration.

    Best Regards,

    Wenyan Zhang


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.