[ios_platform_view, a11y] Make `FlutterPlatformViewSemanticsContainer` a SemanticsObject. (#29531) (#29725)
Co-authored-by: Chris Yang <ychris@google.com>
diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.h b/shell/platform/darwin/ios/framework/Source/SemanticsObject.h
index b2202f0..33fbcde 100644
--- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.h
+++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.h
@@ -62,11 +62,6 @@
@property(nonatomic, strong) NSArray<SemanticsObject*>* children;
/**
- * Used if this SemanticsObject is for a platform view.
- */
-@property(strong, nonatomic) FlutterPlatformViewSemanticsContainer* platformViewSemanticsContainer;
-
-/**
* The UIAccessibility that represents this object.
*
* By default, this return self. Subclasses can override to return different
@@ -159,16 +154,14 @@
* * `SemanticsObject` for the other type of semantics objects.
* * `FlutterSemanticsObject` for default implementation of `SemanticsObject`.
*/
-@interface FlutterPlatformViewSemanticsContainer : UIAccessibilityElement
+@interface FlutterPlatformViewSemanticsContainer : SemanticsObject
-/**
- * The position inside an accessibility container.
- */
-@property(nonatomic) NSInteger index;
+- (instancetype)initWithBridge:(fml::WeakPtr<flutter::AccessibilityBridgeIos>)bridge
+ uid:(int32_t)uid NS_UNAVAILABLE;
-- (instancetype)init __attribute__((unavailable("Use initWithAccessibilityContainer: instead")));
-
-- (instancetype)initWithSemanticsObject:(SemanticsObject*)object;
+- (instancetype)initWithBridge:(fml::WeakPtr<flutter::AccessibilityBridgeIos>)bridge
+ uid:(int32_t)uid
+ platformView:(UIView*)platformView NS_DESIGNATED_INITIALIZER;
@end
diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm
index af44bdd..f2a39da 100644
--- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm
+++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm
@@ -299,7 +299,6 @@
[_children release];
_parent = nil;
_container.get().semanticsObject = nil;
- [_platformViewSemanticsContainer release];
_inDealloc = YES;
[super dealloc];
}
@@ -318,9 +317,6 @@
}
- (BOOL)hasChildren {
- if (_node.IsPlatformViewNode()) {
- return YES;
- }
return [self.children count] != 0;
}
@@ -752,31 +748,16 @@
@end
@interface FlutterPlatformViewSemanticsContainer ()
-@property(nonatomic, assign) SemanticsObject* semanticsObject;
@property(nonatomic, strong) UIView* platformView;
@end
@implementation FlutterPlatformViewSemanticsContainer
-// Method declared as unavailable in the interface
-- (instancetype)init {
- [self release];
- [super doesNotRecognizeSelector:_cmd];
- return nil;
-}
-
-- (instancetype)initWithSemanticsObject:(SemanticsObject*)object {
- FML_CHECK(object);
- // Initialize with the UIView as the container.
- // The UIView will not necessarily be accessibility parent for this object.
- // The bridge informs the OS of the actual structure via
- // `accessibilityContainer` and `accessibilityElementAtIndex`.
- if (self = [super initWithAccessibilityContainer:object.bridge->view()]) {
- _semanticsObject = object;
- auto controller = object.bridge->GetPlatformViewsController();
- if (controller) {
- _platformView = [controller->GetPlatformViewByID(object.node.platformViewId) retain];
- }
+- (instancetype)initWithBridge:(fml::WeakPtr<flutter::AccessibilityBridgeIos>)bridge
+ uid:(int32_t)uid
+ platformView:(nonnull UIView*)platformView {
+ if (self = [super initWithBridge:bridge uid:uid]) {
+ _platformView = [platformView retain];
}
return self;
}
@@ -789,47 +770,8 @@
#pragma mark - UIAccessibilityContainer overrides
-- (NSInteger)accessibilityElementCount {
- // This container should only contain 2 elements:
- // 1. The semantic object that represents this container.
- // 2. The platform view object.
- return 2;
-}
-
-- (nullable id)accessibilityElementAtIndex:(NSInteger)index {
- FML_DCHECK(index < 2);
- if (index == 0) {
- return _semanticsObject.nativeAccessibility;
- } else {
- return _platformView;
- }
-}
-
-- (NSInteger)indexOfAccessibilityElement:(id)element {
- FML_DCHECK(element == _semanticsObject || element == _platformView);
- if (element == _semanticsObject) {
- return 0;
- } else {
- return 1;
- }
-}
-
-#pragma mark - UIAccessibilityElement overrides
-
-- (CGRect)accessibilityFrame {
- return _semanticsObject.accessibilityFrame;
-}
-
-- (BOOL)isAccessibilityElement {
- return NO;
-}
-
-- (id)accessibilityContainer {
- return [_semanticsObject accessibilityContainer];
-}
-
-- (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
- return [_platformView accessibilityScroll:direction];
+- (NSArray*)accessibilityElements {
+ return @[ _platformView ];
}
@end
@@ -881,12 +823,6 @@
SemanticsObject* child = [_semanticsObject children][index - 1];
- // Swap the original `SemanticsObject` to a `PlatformViewSemanticsContainer`
- if (child.node.IsPlatformViewNode()) {
- child.platformViewSemanticsContainer.index = index;
- return child.platformViewSemanticsContainer;
- }
-
if ([child hasChildren])
return [child accessibilityContainer];
return child.nativeAccessibility;
@@ -896,11 +832,6 @@
if (element == _semanticsObject)
return 0;
- // FlutterPlatformViewSemanticsContainer is always the last element of its parent.
- if ([element isKindOfClass:[FlutterPlatformViewSemanticsContainer class]]) {
- return ((FlutterPlatformViewSemanticsContainer*)element).index;
- }
-
NSArray<SemanticsObject*>* children = [_semanticsObject children];
for (size_t i = 0; i < [children count]; i++) {
SemanticsObject* child = children[i];
diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm
index 6b89404..019fea6 100644
--- a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm
+++ b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm
@@ -690,16 +690,25 @@
XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen);
}
-- (void)testSemanticsObjectAndPlatformViewSemanticsContainerDontHaveRetainCycle {
+- (void)testFlutterPlatformViewSemanticsContainer {
fml::WeakPtrFactory<flutter::MockAccessibilityBridge> factory(
new flutter::MockAccessibilityBridge());
fml::WeakPtr<flutter::MockAccessibilityBridge> bridge = factory.GetWeakPtr();
- SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1];
- object.platformViewSemanticsContainer =
- [[FlutterPlatformViewSemanticsContainer alloc] initWithSemanticsObject:object];
- __weak SemanticsObject* weakObject = object;
- object = nil;
- XCTAssertNil(weakObject);
+ __weak UIView* weakPlatformView;
+ @autoreleasepool {
+ UIView* platformView = [[UIView alloc] init];
+
+ FlutterPlatformViewSemanticsContainer* container =
+ [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge
+ uid:1
+ platformView:platformView];
+ XCTAssertEqualObjects(container.accessibilityElements, @[ platformView ]);
+ weakPlatformView = platformView;
+ XCTAssertNotNil(weakPlatformView);
+ }
+ // Check if there's no more strong references to `platformView` after container and platformView
+ // are released.
+ XCTAssertNil(weakPlatformView);
}
- (void)testFlutterSwitchSemanticsObjectMatchesUISwitch {
diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm
index 0e7a4ac..48f8d97 100644
--- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm
+++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm
@@ -126,15 +126,6 @@
object.accessibilityCustomActions = accessibilityCustomActions;
}
- if (object.node.IsPlatformViewNode()) {
- auto controller = GetPlatformViewsController();
- if (controller) {
- object.platformViewSemanticsContainer = [[[FlutterPlatformViewSemanticsContainer alloc]
- initWithSemanticsObject:object] autorelease];
- }
- } else if (object.platformViewSemanticsContainer) {
- object.platformViewSemanticsContainer = nil;
- }
if (needsAnnouncement) {
// Try to be more polite - iOS 11+ supports
// UIAccessibilitySpeechAttributeQueueAnnouncement which should avoid
@@ -268,6 +259,12 @@
} else if (node.HasFlag(flutter::SemanticsFlags::kHasImplicitScrolling)) {
return [[[FlutterScrollableSemanticsObject alloc] initWithBridge:weak_ptr
uid:node.id] autorelease];
+ } else if (node.IsPlatformViewNode()) {
+ return [[[FlutterPlatformViewSemanticsContainer alloc]
+ initWithBridge:weak_ptr
+ uid:node.id
+ platformView:weak_ptr->GetPlatformViewsController()->GetPlatformViewByID(
+ node.platformViewId)] autorelease];
} else {
return [[[FlutterSemanticsObject alloc] initWithBridge:weak_ptr uid:node.id] autorelease];
}