Skip to content

Commit 678653b

Browse files
author
Chris Yang
authored
Fix iOS platform view's mask view blocking touch events. (flutter#21286)
1 parent f5ee86e commit 678653b

File tree

4 files changed

+87
-6
lines changed

4 files changed

+87
-6
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ - (instancetype)initWithFrame:(CGRect)frame {
9090
return self;
9191
}
9292

93+
// In some scenarios, when we add this view as a maskView of the ChildClippingView, iOS added
94+
// this view as a subview of the ChildClippingView.
95+
// This results this view blocking touch events on the ChildClippingView.
96+
// So we should always ignore any touch events sent to this view.
97+
// See https://github.com/flutter/flutter/issues/66044
98+
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event {
99+
return NO;
100+
}
101+
93102
- (void)drawRect:(CGRect)rect {
94103
CGContextRef context = UIGraphicsGetCurrentContext();
95104
CGContextSaveGState(context);

testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ @implementation AppDelegate
2323
- (BOOL)application:(UIApplication*)application
2424
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
2525
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
26-
26+
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--maskview-blocking"]) {
27+
self.window.tintColor = UIColor.systemPinkColor;
28+
}
2729
NSDictionary<NSString*, NSString*>* launchArgsMap = @{
2830
// The Platform view golden test args should match `PlatformViewGoldenTestManager`.
2931
@"--locale-initialization" : @"locale_initialization",
@@ -58,7 +60,6 @@ - (BOOL)application:(UIApplication*)application
5860
*stop = YES;
5961
}
6062
}];
61-
6263
if (flutterViewControllerTestName) {
6364
[self setupFlutterViewControllerTest:flutterViewControllerTestName];
6465
} else if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--screen-before-flutter"]) {

testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewGestureRecognizerTests.m

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,54 @@ - (void)testAccept {
110110
[[XCTNSPredicateExpectation alloc] initWithPredicate:predicate object:platformView];
111111

112112
[platformView tap];
113+
114+
[self waitForExpectations:@[ expection ] timeout:kSecondsToWaitForPlatformView];
115+
XCTAssertEqualObjects(platformView.label,
116+
@"-gestureTouchesBegan-gestureTouchesEnded-platformViewTapped");
117+
}
118+
119+
- (void)testGestureWithMaskViewBlockingPlatformView {
120+
XCUIApplication* app = [[XCUIApplication alloc] init];
121+
app.launchArguments = @[ @"--gesture-accept", @"--maskview-blocking" ];
122+
[app launch];
123+
124+
NSPredicate* predicateToFindPlatformView =
125+
[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject,
126+
NSDictionary<NSString*, id>* _Nullable bindings) {
127+
XCUIElement* element = evaluatedObject;
128+
return [element.identifier hasPrefix:@"platform_view"];
129+
}];
130+
XCUIElement* platformView = [app.textViews elementMatchingPredicate:predicateToFindPlatformView];
131+
if (![platformView waitForExistenceWithTimeout:kSecondsToWaitForPlatformView]) {
132+
NSLog(@"%@", app.debugDescription);
133+
XCTFail(@"Failed due to not able to find any platformView with %@ seconds",
134+
@(kSecondsToWaitForPlatformView));
135+
}
136+
137+
XCTAssertNotNil(platformView);
138+
XCTAssertEqualObjects(platformView.label, @"");
139+
140+
NSPredicate* predicate = [NSPredicate
141+
predicateWithFormat:@"label == %@",
142+
@"-gestureTouchesBegan-gestureTouchesEnded-platformViewTapped"];
143+
XCTNSPredicateExpectation* expection =
144+
[[XCTNSPredicateExpectation alloc] initWithPredicate:predicate object:platformView];
145+
146+
XCUICoordinate* coordinate =
147+
[self getNormalizedCoordinate:app
148+
point:CGVectorMake(platformView.frame.origin.x + 10,
149+
platformView.frame.origin.y + 10)];
150+
[coordinate tap];
151+
113152
[self waitForExpectations:@[ expection ] timeout:kSecondsToWaitForPlatformView];
114153
XCTAssertEqualObjects(platformView.label,
115154
@"-gestureTouchesBegan-gestureTouchesEnded-platformViewTapped");
116155
}
117156

157+
- (XCUICoordinate*)getNormalizedCoordinate:(XCUIApplication*)app point:(CGVector)vector {
158+
XCUICoordinate* appZero = [app coordinateWithNormalizedOffset:CGVectorMake(0, 0)];
159+
XCUICoordinate* coordinate = [appZero coordinateWithOffset:vector];
160+
return coordinate;
161+
}
162+
118163
@end

testing/scenario_app/lib/src/platform_view.dart

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -336,9 +336,9 @@ class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BaseP
336336
MultiPlatformViewBackgroundForegroundScenario(Window window, {this.firstId, this.secondId})
337337
: assert(window != null),
338338
super(window) {
339+
_nextFrame = _firstFrame;
339340
createPlatformView(window, 'platform view 1', firstId);
340341
createPlatformView(window, 'platform view 2', secondId);
341-
_nextFrame = _firstFrame;
342342
}
343343

344344
/// The platform view identifier to use for the first platform view.
@@ -532,6 +532,8 @@ class PlatformViewForTouchIOSScenario extends Scenario
532532

533533
int _viewId;
534534
bool _accept;
535+
536+
VoidCallback _nextFrame;
535537
/// Creates the PlatformView scenario.
536538
///
537539
/// The [window] parameter must not be null.
@@ -545,14 +547,24 @@ class PlatformViewForTouchIOSScenario extends Scenario
545547
} else {
546548
createPlatformView(window, text, id);
547549
}
550+
_nextFrame = _firstFrame;
548551
}
549552

550553
@override
551554
void onBeginFrame(Duration duration) {
552-
final SceneBuilder builder = SceneBuilder();
555+
_nextFrame();
556+
}
553557

554-
builder.pushOffset(0, 0);
555-
finishBuilderByAddingPlatformViewAndPicture(builder, _viewId);
558+
@override
559+
void onDrawFrame() {
560+
// Some iOS gesture recognizers bugs are introduced in the second frame (with a different platform view rect) after laying out the platform view.
561+
// So in this test, we load 2 frames to ensure that we cover those cases.
562+
// See https://github.com/flutter/flutter/issues/66044
563+
if (_nextFrame == _firstFrame) {
564+
_nextFrame = _secondFrame;
565+
window.scheduleFrame();
566+
}
567+
super.onDrawFrame();
556568
}
557569

558570
@override
@@ -585,6 +597,20 @@ class PlatformViewForTouchIOSScenario extends Scenario
585597
}
586598

587599
}
600+
601+
void _firstFrame() {
602+
final SceneBuilder builder = SceneBuilder();
603+
604+
builder.pushOffset(0, 0);
605+
finishBuilderByAddingPlatformViewAndPicture(builder, _viewId);
606+
}
607+
608+
void _secondFrame() {
609+
final SceneBuilder builder = SceneBuilder();
610+
611+
builder.pushOffset(5, 5);
612+
finishBuilderByAddingPlatformViewAndPicture(builder, _viewId);
613+
}
588614
}
589615

590616
mixin _BasePlatformViewScenarioMixin on Scenario {

0 commit comments

Comments
 (0)