Skip to content

Commit 5d08804

Browse files
authored
Makes scrollable to use main screen if the flutter view is not attached to a screen (flutter#28110)
1 parent bf63412 commit 5d08804

File tree

2 files changed

+76
-2
lines changed

2 files changed

+76
-2
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ CGPoint ConvertPointToGlobal(SemanticsObject* reference, CGPoint local_point) {
5454
// `rect` is in the physical pixel coordinate system. iOS expects the accessibility frame in
5555
// the logical pixel coordinate system. Therefore, we divide by the `scale` (pixel ratio) to
5656
// convert.
57-
CGFloat scale = [[[reference bridge]->view() window] screen].scale;
57+
UIScreen* screen = [[[reference bridge]->view() window] screen];
58+
// Screen can be nil if the FlutterView is covered by another native view.
59+
CGFloat scale = screen == nil ? [UIScreen mainScreen].scale : screen.scale;
5860
auto result = CGPointMake(point.x() / scale, point.y() / scale);
5961
return [[reference bridge]->view() convertPoint:result toView:nil];
6062
}
@@ -80,7 +82,9 @@ CGRect ConvertRectToGlobal(SemanticsObject* reference, CGRect local_rect) {
8082
// `rect` is in the physical pixel coordinate system. iOS expects the accessibility frame in
8183
// the logical pixel coordinate system. Therefore, we divide by the `scale` (pixel ratio) to
8284
// convert.
83-
CGFloat scale = [[[reference bridge]->view() window] screen].scale;
85+
UIScreen* screen = [[[reference bridge]->view() window] screen];
86+
// Screen can be nil if the FlutterView is covered by another native view.
87+
CGFloat scale = screen == nil ? [UIScreen mainScreen].scale : screen.scale;
8488
auto result =
8589
CGRectMake(rect.x() / scale, rect.y() / scale, rect.width() / scale, rect.height() / scale);
8690
return UIAccessibilityConvertFrameToScreenCoordinates(result, [reference bridge]->view());

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

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,36 @@ void AccessibilityObjectDidLoseFocus(int32_t id) override {}
5656
UIView* view_;
5757
UIWindow* window_;
5858
};
59+
60+
class MockAccessibilityBridgeNoWindow : public AccessibilityBridgeIos {
61+
public:
62+
MockAccessibilityBridgeNoWindow() : observations({}) {
63+
view_ = [[UIView alloc] initWithFrame:kScreenSize];
64+
}
65+
bool isVoiceOverRunning() const override { return isVoiceOverRunningValue; }
66+
UIView* view() const override { return view_; }
67+
UIView<UITextInput>* textInputView() override { return nil; }
68+
void DispatchSemanticsAction(int32_t id, SemanticsAction action) override {
69+
SemanticsActionObservation observation(id, action);
70+
observations.push_back(observation);
71+
}
72+
void DispatchSemanticsAction(int32_t id,
73+
SemanticsAction action,
74+
fml::MallocMapping args) override {
75+
SemanticsActionObservation observation(id, action);
76+
observations.push_back(observation);
77+
}
78+
void AccessibilityObjectDidBecomeFocused(int32_t id) override {}
79+
void AccessibilityObjectDidLoseFocus(int32_t id) override {}
80+
std::shared_ptr<FlutterPlatformViewsController> GetPlatformViewsController() const override {
81+
return nil;
82+
}
83+
std::vector<SemanticsActionObservation> observations;
84+
bool isVoiceOverRunningValue;
85+
86+
private:
87+
UIView* view_;
88+
};
5989
} // namespace
6090
} // namespace flutter
6191

@@ -208,6 +238,46 @@ - (void)testVerticalFlutterScrollableSemanticsObject {
208238
CGPointMake(0, scrollPosition * effectivelyScale)));
209239
}
210240

241+
- (void)testVerticalFlutterScrollableSemanticsObjectNoWindow {
242+
fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
243+
new flutter::MockAccessibilityBridgeNoWindow());
244+
fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
245+
246+
float transformScale = 0.5f;
247+
float screenScale =
248+
[UIScreen mainScreen].scale; // Flutter view without window uses [UIScreen mainScreen];
249+
float effectivelyScale = transformScale / screenScale;
250+
float x = 10;
251+
float y = 10;
252+
float w = 100;
253+
float h = 200;
254+
float scrollExtentMax = 500.0;
255+
float scrollPosition = 150.0;
256+
257+
flutter::SemanticsNode node;
258+
node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
259+
node.actions = flutter::kVerticalScrollSemanticsActions;
260+
node.rect = SkRect::MakeXYWH(x, y, w, h);
261+
node.scrollExtentMax = scrollExtentMax;
262+
node.scrollPosition = scrollPosition;
263+
node.transform = {
264+
transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, transformScale, 0, 0, 0, 0, 1.0};
265+
FlutterSemanticsObject* delegate = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
266+
FlutterScrollableSemanticsObject* scrollable =
267+
[[FlutterScrollableSemanticsObject alloc] initWithSemanticsObject:delegate];
268+
SemanticsObject* scrollable_object = static_cast<SemanticsObject*>(scrollable);
269+
[scrollable_object setSemanticsNode:&node];
270+
[scrollable_object accessibilityBridgeDidFinishUpdate];
271+
XCTAssertTrue(
272+
CGRectEqualToRect(scrollable.frame, CGRectMake(x * effectivelyScale, y * effectivelyScale,
273+
w * effectivelyScale, h * effectivelyScale)));
274+
XCTAssertTrue(CGSizeEqualToSize(
275+
scrollable.contentSize,
276+
CGSizeMake(w * effectivelyScale, (h + scrollExtentMax) * effectivelyScale)));
277+
XCTAssertTrue(CGPointEqualToPoint(scrollable.contentOffset,
278+
CGPointMake(0, scrollPosition * effectivelyScale)));
279+
}
280+
211281
- (void)testHorizontalFlutterScrollableSemanticsObject {
212282
fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
213283
new flutter::MockAccessibilityBridge());

0 commit comments

Comments
 (0)