Skip to content
Merged
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue19168.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 19168, "iOS Picker dismiss does not work when clicking outside of the Picker", PlatformAffected.iOS | PlatformAffected.macOS)]
public class Issue19168 : ContentPage
{
Picker picker;
public Issue19168()
{
picker = new Picker
{
ItemsSource = new List<string>
{
"Baboon",
"Capuchin Monkey",
"Blue Monkey",
"Squirrel Monkey",
"Golden Lion Tamarin",
"Howler Monkey",
"Japanese Macaque"
},
AutomationId = "Picker"
};

var counterBtn = new Button
{
Text = "Click me",
AutomationId = "Button",
VerticalOptions = LayoutOptions.End,
HorizontalOptions = LayoutOptions.Fill
};
Content = new ScrollView
{
Content = new VerticalStackLayout
{
Padding = new Thickness(30, 0),
Spacing = 25,
Children =
{
picker,
counterBtn,
}
}
};
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue19168 : _IssuesUITest
{
public Issue19168(TestDevice testDevice) : base(testDevice)
{
}
public override string Issue => "iOS Picker dismiss does not work when clicking outside of the Picker";

[Test]
[Category(UITestCategories.Picker)]
public void PickerShouldDismissWhenClickOnOutside()
{
App.WaitForElement("Picker");
App.Tap("Picker");
#if MACCATALYST
App.TapCoordinates(600, 200);
App.WaitForElement("Button");
#elif ANDROID
App.TapCoordinates(0, 100);
#elif WINDOWS
App.TapCoordinates(60, 600);
#elif IOS
App.Tap("Button");
#endif
Comment on lines +20 to +29
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

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

Platform-specific conditional compilation violates the UI testing guidelines. According to the coding guidelines, tests should run on all applicable platforms by default unless there's a specific technical limitation. This test uses different approaches for each platform (coordinates vs button tap), which makes the test inconsistent across platforms. Consider implementing a unified approach that works across all platforms, or if platform-specific behavior is truly necessary, document why each platform requires different testing logic.

Copilot generated this review using guidance from repository custom instructions.
VerifyScreenshot();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pending snapshot on Mac:
image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Pending snapshot on Mac: image

I have added the Mac snapshot.

}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 59 additions & 0 deletions src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public partial class PickerHandler : ViewHandler<IPicker, MauiPicker>
{
readonly MauiPickerProxy _proxy = new();
UIPickerView? _pickerView;
UITapGestureRecognizer? _tapGestureRecognizer;

#if MACCATALYST
UIAlertController? _pickerController;
Expand Down Expand Up @@ -136,6 +137,7 @@ protected override void ConnectHandler(MauiPicker platformView)
protected override void DisconnectHandler(MauiPicker platformView)
{
_proxy.Disconnect(platformView);
RemoveTouchDismissGesture();

#if MACCATALYST
if (_pickerController != null)
Expand Down Expand Up @@ -269,6 +271,60 @@ void FinishSelectItem(UIPickerView? pickerView, MauiPicker textField)
textField.ResignFirstResponder();
}

void SetupTouchDismissGesture()
{
// Don't setup if window isn't available yet
if (PlatformView?.Window is null)
{
return;
}

// Don't setup if already configured
if (_tapGestureRecognizer is not null)
{
return;
}
var weakHandler = new WeakReference<PickerHandler>(this);
_tapGestureRecognizer = new UITapGestureRecognizer(() =>
{
if (weakHandler.TryGetTarget(out var handler))
{
handler.DismissPicker();
}
});
_tapGestureRecognizer.CancelsTouchesInView = false;
PlatformView.Window.AddGestureRecognizer(_tapGestureRecognizer);
}

void DismissPicker()
{
#if MACCATALYST
// On MacCatalyst, dismiss the UIAlertController and clean up the tap gesture directly,
// since ResignFirstResponder is not called here and OnEnded will not fire.
if (_pickerController is not null)
{
_pickerController.DismissViewController(true, null);
if (VirtualView is IPicker virtualView)
virtualView.IsFocused = virtualView.IsOpen = false;
}
RemoveTouchDismissGesture();
#else
// On iOS, dismiss by ending editing
PlatformView?.EndEditing(true);
#endif
}


void RemoveTouchDismissGesture()
{
if (_tapGestureRecognizer is not null)
{
_tapGestureRecognizer.View?.RemoveGestureRecognizer(_tapGestureRecognizer);
_tapGestureRecognizer.Dispose();
_tapGestureRecognizer = null;
}
}

class MauiPickerProxy
{
WeakReference<PickerHandler>? _handler;
Expand All @@ -290,6 +346,7 @@ public void Connect(PickerHandler handler, IPicker virtualView, MauiPicker platf

public void Disconnect(MauiPicker platformView)
{
Handler?.RemoveTouchDismissGesture();
platformView.EditingDidBegin -= OnStarted;
platformView.EditingDidEnd -= OnEnded;
platformView.EditingChanged -= OnEditing;
Expand Down Expand Up @@ -322,10 +379,12 @@ void OnStarted(object? sender, EventArgs eventArgs)
int selectedIndex = handler.VirtualView?.SelectedIndex ?? 0;
handler.DisplayAlert(handler.PlatformView, selectedIndex);
#endif
Handler?.SetupTouchDismissGesture();
}

void OnEnded(object? sender, EventArgs eventArgs)
{
Handler?.RemoveTouchDismissGesture();
if (Handler is not PickerHandler handler || handler._pickerView is not UIPickerView pickerView)
return;

Expand Down
Loading