[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];
   }