diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue33463.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue33463.cs new file mode 100644 index 000000000000..7b109b0a95e4 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue33463.cs @@ -0,0 +1,30 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 33463, "[macOS]Picker items are not visible", PlatformAffected.macOS)] +public class Issue33463 : TestContentPage +{ + protected override void Init() + { + var picker = new Picker + { + AutomationId = "TestPicker", + Title = "Select an item" + }; + picker.Items.Add("Item 1"); + picker.Items.Add("Item 2"); + picker.Items.Add("Item 3"); + + var entry = new Entry + { + AutomationId = "TestEntry", + Placeholder = "Entry for TAB focus" + }; + + Content = new VerticalStackLayout + { + Padding = 12, + Spacing = 10, + Children = { picker, entry } + }; + } +} diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33463.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33463.cs new file mode 100644 index 000000000000..1f985fe69d3e --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33463.cs @@ -0,0 +1,34 @@ +#if MACCATALYST // The reported scenario is specific to macOS, where the picker dialog closes automatically when opened using the Tab key, so this test is enabled only for macOS. +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue33463 : _IssuesUITest +{ + public Issue33463(TestDevice testDevice) : base(testDevice) + { + } + + public override string Issue => "[macOS]Picker items are not visible"; + + [Test] + [Category(UITestCategories.Picker)] + public void PickerShouldRemainOpenWhenOpenedUsingTabKey() + { + App.WaitForElement("TestPicker"); + + App.Tap("TestPicker"); + App.WaitForElement("Done"); + App.Tap("Done"); + + App.SendTabKey(); + Task.Delay(800).Wait(); + + var doneButton = App.FindElement("Done"); + Assert.That(doneButton, Is.Not.Null, + "The picker dialog should remain open when it is opened using the Tab key."); + } +} +#endif diff --git a/src/Core/src/Handlers/Picker/PickerHandler.cs b/src/Core/src/Handlers/Picker/PickerHandler.cs index b56b5ce5b9f6..48a4e5d7f7ba 100644 --- a/src/Core/src/Handlers/Picker/PickerHandler.cs +++ b/src/Core/src/Handlers/Picker/PickerHandler.cs @@ -33,7 +33,7 @@ public partial class PickerHandler : IPickerHandler public static CommandMapper CommandMapper = new(ViewCommandMapper) { -#if ANDROID +#if ANDROID || MACCATALYST [nameof(IPicker.Focus)] = MapFocus, [nameof(IPicker.Unfocus)] = MapUnfocus #endif diff --git a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs index 2a44b53469f2..06e835e2e966 100644 --- a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs +++ b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs @@ -9,6 +9,9 @@ public partial class PickerHandler : ViewHandler { readonly MauiPickerProxy _proxy = new(); UIPickerView? _pickerView; +#if MACCATALYST + UIAlertController? _currentPickerController; +#endif #if !MACCATALYST protected override MauiPicker CreatePlatformView() @@ -40,6 +43,13 @@ protected override MauiPicker CreatePlatformView() => void DisplayAlert(MauiPicker uITextField, int selectedIndex) { + // Guard against multiple picker controllers being displayed simultaneously + if (_currentPickerController != null) + { + _currentPickerController.DismissViewController(true, null); + _currentPickerController = null; + } + var paddingTitle = 0; if (!string.IsNullOrEmpty(VirtualView.Title)) paddingTitle += 25; @@ -59,7 +69,8 @@ void DisplayAlert(MauiPicker uITextField, int selectedIndex) // The UIPickerView is displayed as a subview of the UIAlertController when an empty string is provided as the title, instead of using the VirtualView title. // This behavior deviates from the expected native macOS behavior. - var pickerController = UIAlertController.Create("", "", UIAlertControllerStyle.ActionSheet); + _currentPickerController = UIAlertController.Create("", "", UIAlertControllerStyle.ActionSheet); + var pickerController = _currentPickerController; // needs translation pickerController.AddAction(UIAlertAction.Create("Done", @@ -84,11 +95,13 @@ void DisplayAlert(MauiPicker uITextField, int selectedIndex) EventHandler? editingDidEndHandler = null; - editingDidEndHandler = async (s, e) => + editingDidEndHandler = (s, e) => { - await pickerController.DismissViewControllerAsync(true); if (VirtualView is IPicker virtualView) virtualView.IsFocused = virtualView.IsOpen = false; + + _currentPickerController = null; + uITextField.EditingDidEnd -= editingDidEndHandler; }; @@ -202,6 +215,32 @@ internal static void MapIsOpen(IPickerHandler handler, IPicker picker) handler.PlatformView?.UpdateIsOpen(picker); } +#if MACCATALYST + internal static void MapFocus(IPickerHandler handler, IPicker picker, object? args) + { + if (handler.IsConnected() && handler is PickerHandler) + { + ViewHandler.MapFocus(handler, picker, args); + } + } + + internal static void MapUnfocus(IPickerHandler handler, IPicker picker, object? args) + { + if (handler.IsConnected() && handler is PickerHandler pickerHandler) + { + // Dismiss the picker controller when Unfocus() is explicitly called + if (pickerHandler._currentPickerController != null) + { + var controller = pickerHandler._currentPickerController; + pickerHandler._currentPickerController = null; + controller.DismissViewController(true, null); + } + + ViewHandler.MapUnfocus(handler, picker, args); + } + } +#endif + void UpdatePickerFromPickerSource(PickerSource? pickerSource) { if (VirtualView == null || PlatformView == null || pickerSource == null)