diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm index 68f1beb7c31bc..6e2f9c5a20160 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm @@ -460,6 +460,10 @@ - (NSAttributedString*)createAttributedStringFromString:(NSString*)string return attributedString; } +- (void)showOnScreen { + [self bridge]->DispatchSemanticsAction([self uid], flutter::SemanticsAction::kShowOnScreen); +} + #pragma mark - UIAccessibility overrides - (BOOL)isAccessibilityElement { @@ -561,14 +565,32 @@ - (SemanticsObject*)search:(CGPoint)point { return nil; } -// Overrides apple private method to fix https://github.com/flutter/flutter/issues/113377. -// For overlapping UIAccessibilityElements (e.g. a stack) in IOS, the focus goes to the smallest -// object before IOS 16, but to the top-left object in IOS 16. -// Overrides this method to focus the first eligiable semantics object in hit test order. +// iOS uses this method to determine the hittest results when users touch +// explore in VoiceOver. +// +// For overlapping UIAccessibilityElements (e.g. a stack) in IOS, the focus +// goes to the smallest object before IOS 16, but to the top-left object in +// IOS 16. Overrides this method to focus the first eligiable semantics +// object in hit test order. - (id)_accessibilityHitTest:(CGPoint)point withEvent:(UIEvent*)event { return [self search:point]; } +// iOS calls this method when this item is swipe-to-focusd in VoiceOver. +- (BOOL)accessibilityScrollToVisible { + [self showOnScreen]; + return YES; +} + +// iOS calls this method when this item is swipe-to-focusd in VoiceOver. +- (BOOL)accessibilityScrollToVisibleWithChild:(id)child { + if ([child isKindOfClass:[SemanticsObject class]]) { + [child showOnScreen]; + return YES; + } + return NO; +} + - (NSAttributedString*)accessibilityAttributedLabel { NSString* label = [self accessibilityLabel]; if (label.length == 0) { @@ -750,7 +772,7 @@ - (void)accessibilityElementDidBecomeFocused { [self bridge]->AccessibilityObjectDidBecomeFocused([self uid]); if ([self node].HasFlag(flutter::SemanticsFlags::kIsHidden) || [self node].HasFlag(flutter::SemanticsFlags::kIsHeader)) { - [self bridge]->DispatchSemanticsAction([self uid], flutter::SemanticsAction::kShowOnScreen); + [self showOnScreen]; } if ([self node].HasAction(flutter::SemanticsAction::kDidGainAccessibilityFocus)) { [self bridge]->DispatchSemanticsAction([self uid], diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm index cc2e29a126a92..48ebd0063563a 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm @@ -93,7 +93,8 @@ @interface SemanticsObjectTest : XCTestCase @end @interface SemanticsObject (Tests) - +- (BOOL)accessibilityScrollToVisible; +- (BOOL)accessibilityScrollToVisibleWithChild:(id)child; - (id)_accessibilityHitTest:(CGPoint)point withEvent:(UIEvent*)event; @end @@ -202,6 +203,42 @@ - (void)testAccessibilityHitTestNoFocusableItem { XCTAssertNil(hitTestResult); } +- (void)testAccessibilityScrollToVisible { + fml::WeakPtrFactory factory( + new flutter::MockAccessibilityBridge()); + fml::WeakPtr bridge = factory.GetWeakPtr(); + SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3]; + + flutter::SemanticsNode node3; + node3.id = 3; + node3.rect = SkRect::MakeXYWH(0, 0, 200, 200); + [object3 setSemanticsNode:&node3]; + + [object3 accessibilityScrollToVisible]; + + XCTAssertTrue(bridge->observations.size() == 1); + XCTAssertTrue(bridge->observations[0].id == 3); + XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen); +} + +- (void)testAccessibilityScrollToVisibleWithChild { + fml::WeakPtrFactory factory( + new flutter::MockAccessibilityBridge()); + fml::WeakPtr bridge = factory.GetWeakPtr(); + SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3]; + + flutter::SemanticsNode node3; + node3.id = 3; + node3.rect = SkRect::MakeXYWH(0, 0, 200, 200); + [object3 setSemanticsNode:&node3]; + + [object3 accessibilityScrollToVisibleWithChild:object3]; + + XCTAssertTrue(bridge->observations.size() == 1); + XCTAssertTrue(bridge->observations[0].id == 3); + XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen); +} + - (void)testAccessibilityHitTestOutOfRect { fml::WeakPtrFactory factory( new flutter::MockAccessibilityBridge());