Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue33463.cs
Original file line number Diff line number Diff line change
@@ -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 }
};
}
}
Original file line number Diff line number Diff line change
@@ -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");
Comment on lines +27 to +29
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After SendTabKey(), the test should verify that the picker opens before checking for the Done button. Currently, it relies on a fixed delay and then uses FindElement which may return null without waiting. If the picker hasn't opened yet, FindElement("Done") will fail.

The test should use WaitForElement instead of Task.Delay + FindElement:

  • Remove: Task.Delay(800).Wait(); var doneButton = App.FindElement("Done");
  • Use: var doneButton = App.WaitForElement("Done", timeout: TimeSpan.FromSeconds(2));

This ensures the test waits for the picker to open rather than assuming 800ms is sufficient.

Copilot generated this review using guidance from repository custom instructions.
Assert.That(doneButton, Is.Not.Null,
"The picker dialog should remain open when it is opened using the Tab key.");
}
}
#endif
2 changes: 1 addition & 1 deletion src/Core/src/Handlers/Picker/PickerHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public partial class PickerHandler : IPickerHandler

public static CommandMapper<IPicker, IPickerHandler> CommandMapper = new(ViewCommandMapper)
{
#if ANDROID
#if ANDROID || MACCATALYST
[nameof(IPicker.Focus)] = MapFocus,
[nameof(IPicker.Unfocus)] = MapUnfocus
#endif
Expand Down
38 changes: 35 additions & 3 deletions src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ public partial class PickerHandler : ViewHandler<IPicker, MauiPicker>
{
readonly MauiPickerProxy _proxy = new();
UIPickerView? _pickerView;
#if MACCATALYST
UIAlertController? _currentPickerController;
#endif

#if !MACCATALYST
protected override MauiPicker CreatePlatformView()
Expand Down Expand Up @@ -59,7 +62,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;
Comment on lines +65 to +66
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If DisplayAlert is called while _currentPickerController is not null (for example, if the picker is already open and DisplayAlert is called again), the code will create a new UIAlertController and overwrite the _currentPickerController reference without dismissing the previous one. This could lead to multiple picker dialogs being displayed simultaneously or leaked view controllers.

Consider adding a guard at the start of DisplayAlert to check if _currentPickerController is not null, and if so, either dismiss it first or return early to prevent duplicate displays.

Copilot uses AI. Check for mistakes.

// needs translation
pickerController.AddAction(UIAlertAction.Create("Done",
Expand All @@ -84,11 +88,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;
};

Expand Down Expand Up @@ -202,6 +208,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)
Expand Down
Loading