From 5d08804d394e0ec84fa1b5ca97f56d20092f4739 Mon Sep 17 00:00:00 2001 From: chunhtai <47866232+chunhtai@users.noreply.github.com> Date: Wed, 18 Aug 2021 16:47:01 -0700 Subject: [PATCH] Makes scrollable to use main screen if the flutter view is not attached to a screen (#28110) --- .../ios/framework/Source/SemanticsObject.mm | 8 ++- .../framework/Source/SemanticsObjectTest.mm | 70 +++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm index cddce14dca..04048ea8d6 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm @@ -54,7 +54,9 @@ CGPoint ConvertPointToGlobal(SemanticsObject* reference, CGPoint local_point) { // `rect` is in the physical pixel coordinate system. iOS expects the accessibility frame in // the logical pixel coordinate system. Therefore, we divide by the `scale` (pixel ratio) to // convert. - CGFloat scale = [[[reference bridge]->view() window] screen].scale; + UIScreen* screen = [[[reference bridge]->view() window] screen]; + // Screen can be nil if the FlutterView is covered by another native view. + CGFloat scale = screen == nil ? [UIScreen mainScreen].scale : screen.scale; auto result = CGPointMake(point.x() / scale, point.y() / scale); return [[reference bridge]->view() convertPoint:result toView:nil]; } @@ -80,7 +82,9 @@ CGRect ConvertRectToGlobal(SemanticsObject* reference, CGRect local_rect) { // `rect` is in the physical pixel coordinate system. iOS expects the accessibility frame in // the logical pixel coordinate system. Therefore, we divide by the `scale` (pixel ratio) to // convert. - CGFloat scale = [[[reference bridge]->view() window] screen].scale; + UIScreen* screen = [[[reference bridge]->view() window] screen]; + // Screen can be nil if the FlutterView is covered by another native view. + CGFloat scale = screen == nil ? [UIScreen mainScreen].scale : screen.scale; auto result = CGRectMake(rect.x() / scale, rect.y() / scale, rect.width() / scale, rect.height() / scale); return UIAccessibilityConvertFrameToScreenCoordinates(result, [reference bridge]->view()); diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm index a1d164dff8..4dfe87b57c 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm @@ -56,6 +56,36 @@ class MockAccessibilityBridge : public AccessibilityBridgeIos { UIView* view_; UIWindow* window_; }; + +class MockAccessibilityBridgeNoWindow : public AccessibilityBridgeIos { + public: + MockAccessibilityBridgeNoWindow() : observations({}) { + view_ = [[UIView alloc] initWithFrame:kScreenSize]; + } + bool isVoiceOverRunning() const override { return isVoiceOverRunningValue; } + UIView* view() const override { return view_; } + UIView* textInputView() override { return nil; } + void DispatchSemanticsAction(int32_t id, SemanticsAction action) override { + SemanticsActionObservation observation(id, action); + observations.push_back(observation); + } + void DispatchSemanticsAction(int32_t id, + SemanticsAction action, + fml::MallocMapping args) override { + SemanticsActionObservation observation(id, action); + observations.push_back(observation); + } + void AccessibilityObjectDidBecomeFocused(int32_t id) override {} + void AccessibilityObjectDidLoseFocus(int32_t id) override {} + std::shared_ptr GetPlatformViewsController() const override { + return nil; + } + std::vector observations; + bool isVoiceOverRunningValue; + + private: + UIView* view_; +}; } // namespace } // namespace flutter @@ -208,6 +238,46 @@ class MockAccessibilityBridge : public AccessibilityBridgeIos { CGPointMake(0, scrollPosition * effectivelyScale))); } +- (void)testVerticalFlutterScrollableSemanticsObjectNoWindow { + fml::WeakPtrFactory factory( + new flutter::MockAccessibilityBridgeNoWindow()); + fml::WeakPtr bridge = factory.GetWeakPtr(); + + float transformScale = 0.5f; + float screenScale = + [UIScreen mainScreen].scale; // Flutter view without window uses [UIScreen mainScreen]; + float effectivelyScale = transformScale / screenScale; + float x = 10; + float y = 10; + float w = 100; + float h = 200; + float scrollExtentMax = 500.0; + float scrollPosition = 150.0; + + flutter::SemanticsNode node; + node.flags = static_cast(flutter::SemanticsFlags::kHasImplicitScrolling); + node.actions = flutter::kVerticalScrollSemanticsActions; + node.rect = SkRect::MakeXYWH(x, y, w, h); + node.scrollExtentMax = scrollExtentMax; + node.scrollPosition = scrollPosition; + node.transform = { + transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0}; + FlutterSemanticsObject* delegate = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0]; + FlutterScrollableSemanticsObject* scrollable = + [[FlutterScrollableSemanticsObject alloc] initWithSemanticsObject:delegate]; + SemanticsObject* scrollable_object = static_cast(scrollable); + [scrollable_object setSemanticsNode:&node]; + [scrollable_object accessibilityBridgeDidFinishUpdate]; + XCTAssertTrue( + CGRectEqualToRect(scrollable.frame, CGRectMake(x * effectivelyScale, y * effectivelyScale, + w * effectivelyScale, h * effectivelyScale))); + XCTAssertTrue(CGSizeEqualToSize( + scrollable.contentSize, + CGSizeMake(w * effectivelyScale, (h + scrollExtentMax) * effectivelyScale))); + XCTAssertTrue(CGPointEqualToPoint(scrollable.contentOffset, + CGPointMake(0, scrollPosition * effectivelyScale))); +} + - (void)testHorizontalFlutterScrollableSemanticsObject { fml::WeakPtrFactory factory( new flutter::MockAccessibilityBridge()); -- GitLab