Skip to content
Draft
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
13 changes: 13 additions & 0 deletions src/Core/src/Handlers/DatePicker/DatePickerHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,26 @@ static void OnValueChanged(object? sender)
static void OnStarted(object? sender)
{
if (sender is IDatePickerHandler datePickerHandler && datePickerHandler.VirtualView != null)
{
datePickerHandler.VirtualView.IsFocused = true;

// Notify VoiceOver that the date picker popup has appeared
if (datePickerHandler.PlatformView?.InputView is not null)
{
datePickerHandler.PlatformView.PostAccessibilityFocusNotification(datePickerHandler.PlatformView.InputView);
}
}
}

static void OnEnded(object? sender)
{
if (sender is IDatePickerHandler datePickerHandler && datePickerHandler.VirtualView != null)
{
datePickerHandler.VirtualView.IsFocused = false;

// Restore VoiceOver focus to the date picker field when the popup closes
datePickerHandler.PlatformView?.PostAccessibilityFocusNotification();
}
}

static void OnDoneClicked(object? sender)
Expand Down
15 changes: 15 additions & 0 deletions src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,12 @@ void OnStarted(object? sender, EventArgs eventArgs)
{
if (VirtualView is IPicker virtualView)
virtualView.IsFocused = true;

// Notify VoiceOver that the picker popup has appeared
if (sender is MauiPicker platformView && platformView.InputView != null)
{
platformView.PostAccessibilityFocusNotification(platformView.InputView);
}
#if MACCATALYST
if (Handler is not PickerHandler handler)
return;
Expand All @@ -286,8 +292,17 @@ void OnEnded(object? sender, EventArgs eventArgs)
{
pickerView.Select(model.SelectedIndex, 0, false);
}

if (VirtualView is IPicker virtualView)
{
virtualView.IsFocused = false;
}

// Restore VoiceOver focus to the picker field when the popup closes
if (sender is MauiPicker platformView)
{
platformView.PostAccessibilityFocusNotification();
}
}

void OnEditing(object? sender, EventArgs eventArgs)
Expand Down
16 changes: 16 additions & 0 deletions src/Core/src/Handlers/TimePicker/TimePickerHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,29 @@ public void Disconnect(MauiTimePicker platformView)
void OnStarted(object? sender, EventArgs eventArgs)
{
if (VirtualView is not null)
{
VirtualView.IsFocused = true;

// Notify VoiceOver that the time picker popup has appeared
if (sender is MauiTimePicker platformView && platformView.Picker is not null)
{
platformView.PostAccessibilityFocusNotification(platformView.Picker);
}
}
}

void OnEnded(object? sender, EventArgs eventArgs)
{
if (VirtualView is not null)
{
VirtualView.IsFocused = false;

// Restore VoiceOver focus to the time picker field when the popup closes
if (sender is MauiTimePicker platformView)
{
platformView.PostAccessibilityFocusNotification();
}
}
}

void OnValueChanged(object? sender, EventArgs e)
Expand Down
40 changes: 40 additions & 0 deletions src/Core/src/Platform/iOS/SemanticExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,47 @@ internal static void UpdateSemantics(this UIBarItem platformView, Semantics? sem

public static void UpdateSemantics(this UIView platformView, IView view) =>
UpdateSemantics(platformView, view?.Semantics);

/// <summary>
/// Posts a VoiceOver screen changed notification to return focus to the specified view.
/// This is typically used when an input view is dismissed and focus should return to the original control.
/// </summary>
/// <param name="platformView">The platform view that should receive focus</param>
internal static void PostAccessibilityFocusNotification(this UIView platformView)
{
// TODO: Make public for .NET 11.

if (UIAccessibility.IsVoiceOverRunning)
{
UIAccessibility.PostNotification(UIAccessibilityPostNotification.ScreenChanged, platformView);
}
}

/// <summary>
/// Posts a VoiceOver screen changed notification for an input view when it becomes visible.
/// This ensures VoiceOver shifts focus to the input view (e.g., UIPickerView, UIDatePicker) when it appears.
/// </summary>
/// <param name="platformView">The platform view that hosts the input view</param>
/// <param name="inputView">The input view that should receive focus</param>
internal static void PostAccessibilityFocusNotification(this UIView platformView, UIView? inputView)
{
// TODO: Make public for .NET 11.

if (inputView == null)
{
return;
}

// Post notification with a delay to ensure the InputView is fully displayed in the window hierarchy
platformView.BeginInvokeOnMainThread(() =>
{
if (UIAccessibility.IsVoiceOverRunning && inputView.Window is not null)
{
UIAccessibility.PostNotification(UIAccessibilityPostNotification.ScreenChanged, inputView);
}
});
}

internal static void UpdateSemantics(this UIView platformView, Semantics? semantics)
{
if (semantics == null)
Expand Down
Loading