Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit aac0919

Browse files
authored
Overrides accessibilityScrollToVisible (#42047)
fixes flutter/flutter#61624 Observed behavior is that this method is called when an item is swipe-to-focusd in VoiceOver. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent 482c99a commit aac0919

File tree

2 files changed

+65
-6
lines changed

2 files changed

+65
-6
lines changed

shell/platform/darwin/ios/framework/Source/SemanticsObject.mm

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,10 @@ - (NSAttributedString*)createAttributedStringFromString:(NSString*)string
460460
return attributedString;
461461
}
462462

463+
- (void)showOnScreen {
464+
[self bridge]->DispatchSemanticsAction([self uid], flutter::SemanticsAction::kShowOnScreen);
465+
}
466+
463467
#pragma mark - UIAccessibility overrides
464468

465469
- (BOOL)isAccessibilityElement {
@@ -561,14 +565,32 @@ - (SemanticsObject*)search:(CGPoint)point {
561565
return nil;
562566
}
563567

564-
// Overrides apple private method to fix https://github.com/flutter/flutter/issues/113377.
565-
// For overlapping UIAccessibilityElements (e.g. a stack) in IOS, the focus goes to the smallest
566-
// object before IOS 16, but to the top-left object in IOS 16.
567-
// Overrides this method to focus the first eligiable semantics object in hit test order.
568+
// iOS uses this method to determine the hittest results when users touch
569+
// explore in VoiceOver.
570+
//
571+
// For overlapping UIAccessibilityElements (e.g. a stack) in IOS, the focus
572+
// goes to the smallest object before IOS 16, but to the top-left object in
573+
// IOS 16. Overrides this method to focus the first eligiable semantics
574+
// object in hit test order.
568575
- (id)_accessibilityHitTest:(CGPoint)point withEvent:(UIEvent*)event {
569576
return [self search:point];
570577
}
571578

579+
// iOS calls this method when this item is swipe-to-focusd in VoiceOver.
580+
- (BOOL)accessibilityScrollToVisible {
581+
[self showOnScreen];
582+
return YES;
583+
}
584+
585+
// iOS calls this method when this item is swipe-to-focusd in VoiceOver.
586+
- (BOOL)accessibilityScrollToVisibleWithChild:(id)child {
587+
if ([child isKindOfClass:[SemanticsObject class]]) {
588+
[child showOnScreen];
589+
return YES;
590+
}
591+
return NO;
592+
}
593+
572594
- (NSAttributedString*)accessibilityAttributedLabel {
573595
NSString* label = [self accessibilityLabel];
574596
if (label.length == 0) {
@@ -750,7 +772,7 @@ - (void)accessibilityElementDidBecomeFocused {
750772
[self bridge]->AccessibilityObjectDidBecomeFocused([self uid]);
751773
if ([self node].HasFlag(flutter::SemanticsFlags::kIsHidden) ||
752774
[self node].HasFlag(flutter::SemanticsFlags::kIsHeader)) {
753-
[self bridge]->DispatchSemanticsAction([self uid], flutter::SemanticsAction::kShowOnScreen);
775+
[self showOnScreen];
754776
}
755777
if ([self node].HasAction(flutter::SemanticsAction::kDidGainAccessibilityFocus)) {
756778
[self bridge]->DispatchSemanticsAction([self uid],

shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ @interface SemanticsObjectTest : XCTestCase
9393
@end
9494

9595
@interface SemanticsObject (Tests)
96-
96+
- (BOOL)accessibilityScrollToVisible;
97+
- (BOOL)accessibilityScrollToVisibleWithChild:(id)child;
9798
- (id)_accessibilityHitTest:(CGPoint)point withEvent:(UIEvent*)event;
9899
@end
99100

@@ -202,6 +203,42 @@ - (void)testAccessibilityHitTestNoFocusableItem {
202203
XCTAssertNil(hitTestResult);
203204
}
204205

206+
- (void)testAccessibilityScrollToVisible {
207+
fml::WeakPtrFactory<flutter::MockAccessibilityBridge> factory(
208+
new flutter::MockAccessibilityBridge());
209+
fml::WeakPtr<flutter::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
210+
SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
211+
212+
flutter::SemanticsNode node3;
213+
node3.id = 3;
214+
node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
215+
[object3 setSemanticsNode:&node3];
216+
217+
[object3 accessibilityScrollToVisible];
218+
219+
XCTAssertTrue(bridge->observations.size() == 1);
220+
XCTAssertTrue(bridge->observations[0].id == 3);
221+
XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
222+
}
223+
224+
- (void)testAccessibilityScrollToVisibleWithChild {
225+
fml::WeakPtrFactory<flutter::MockAccessibilityBridge> factory(
226+
new flutter::MockAccessibilityBridge());
227+
fml::WeakPtr<flutter::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
228+
SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3];
229+
230+
flutter::SemanticsNode node3;
231+
node3.id = 3;
232+
node3.rect = SkRect::MakeXYWH(0, 0, 200, 200);
233+
[object3 setSemanticsNode:&node3];
234+
235+
[object3 accessibilityScrollToVisibleWithChild:object3];
236+
237+
XCTAssertTrue(bridge->observations.size() == 1);
238+
XCTAssertTrue(bridge->observations[0].id == 3);
239+
XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
240+
}
241+
205242
- (void)testAccessibilityHitTestOutOfRect {
206243
fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
207244
new flutter::MockAccessibilityBridge());

0 commit comments

Comments
 (0)