diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm index ff92a7a330793..a6d3d03653b17 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -90,6 +90,15 @@ - (instancetype)initWithFrame:(CGRect)frame { return self; } +// In some scenarios, when we add this view as a maskView of the ChildClippingView, iOS added +// this view as a subview of the ChildClippingView. +// This results this view blocking touch events on the ChildClippingView. +// So we should always ignore any touch events sent to this view. +// See https://github.com/flutter/flutter/issues/66044 +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event { + return NO; +} + - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSaveGState(context); diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m index d09cc4db033c4..b981938a05bfc 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m +++ b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m @@ -23,7 +23,9 @@ @implementation AppDelegate - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - + if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--maskview-blocking"]) { + self.window.tintColor = UIColor.systemPinkColor; + } NSDictionary* launchArgsMap = @{ // The Platform view golden test args should match `PlatformViewGoldenTestManager`. @"--locale-initialization" : @"locale_initialization", @@ -58,7 +60,6 @@ - (BOOL)application:(UIApplication*)application *stop = YES; } }]; - if (flutterViewControllerTestName) { [self setupFlutterViewControllerTest:flutterViewControllerTestName]; } else if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--screen-before-flutter"]) { diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewGestureRecognizerTests.m b/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewGestureRecognizerTests.m index 3d583e1d5e824..6771454c40318 100644 --- a/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewGestureRecognizerTests.m +++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewGestureRecognizerTests.m @@ -110,9 +110,54 @@ - (void)testAccept { [[XCTNSPredicateExpectation alloc] initWithPredicate:predicate object:platformView]; [platformView tap]; + + [self waitForExpectations:@[ expection ] timeout:kSecondsToWaitForPlatformView]; + XCTAssertEqualObjects(platformView.label, + @"-gestureTouchesBegan-gestureTouchesEnded-platformViewTapped"); +} + +- (void)testGestureWithMaskViewBlockingPlatformView { + XCUIApplication* app = [[XCUIApplication alloc] init]; + app.launchArguments = @[ @"--gesture-accept", @"--maskview-blocking" ]; + [app launch]; + + NSPredicate* predicateToFindPlatformView = + [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, + NSDictionary* _Nullable bindings) { + XCUIElement* element = evaluatedObject; + return [element.identifier hasPrefix:@"platform_view"]; + }]; + XCUIElement* platformView = [app.textViews elementMatchingPredicate:predicateToFindPlatformView]; + if (![platformView waitForExistenceWithTimeout:kSecondsToWaitForPlatformView]) { + NSLog(@"%@", app.debugDescription); + XCTFail(@"Failed due to not able to find any platformView with %@ seconds", + @(kSecondsToWaitForPlatformView)); + } + + XCTAssertNotNil(platformView); + XCTAssertEqualObjects(platformView.label, @""); + + NSPredicate* predicate = [NSPredicate + predicateWithFormat:@"label == %@", + @"-gestureTouchesBegan-gestureTouchesEnded-platformViewTapped"]; + XCTNSPredicateExpectation* expection = + [[XCTNSPredicateExpectation alloc] initWithPredicate:predicate object:platformView]; + + XCUICoordinate* coordinate = + [self getNormalizedCoordinate:app + point:CGVectorMake(platformView.frame.origin.x + 10, + platformView.frame.origin.y + 10)]; + [coordinate tap]; + [self waitForExpectations:@[ expection ] timeout:kSecondsToWaitForPlatformView]; XCTAssertEqualObjects(platformView.label, @"-gestureTouchesBegan-gestureTouchesEnded-platformViewTapped"); } +- (XCUICoordinate*)getNormalizedCoordinate:(XCUIApplication*)app point:(CGVector)vector { + XCUICoordinate* appZero = [app coordinateWithNormalizedOffset:CGVectorMake(0, 0)]; + XCUICoordinate* coordinate = [appZero coordinateWithOffset:vector]; + return coordinate; +} + @end diff --git a/testing/scenario_app/lib/src/platform_view.dart b/testing/scenario_app/lib/src/platform_view.dart index e2d0d6a0e3d7b..90fc0693e0433 100644 --- a/testing/scenario_app/lib/src/platform_view.dart +++ b/testing/scenario_app/lib/src/platform_view.dart @@ -336,9 +336,9 @@ class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BaseP MultiPlatformViewBackgroundForegroundScenario(Window window, {this.firstId, this.secondId}) : assert(window != null), super(window) { + _nextFrame = _firstFrame; createPlatformView(window, 'platform view 1', firstId); createPlatformView(window, 'platform view 2', secondId); - _nextFrame = _firstFrame; } /// The platform view identifier to use for the first platform view. @@ -532,6 +532,8 @@ class PlatformViewForTouchIOSScenario extends Scenario int _viewId; bool _accept; + + VoidCallback _nextFrame; /// Creates the PlatformView scenario. /// /// The [window] parameter must not be null. @@ -545,14 +547,24 @@ class PlatformViewForTouchIOSScenario extends Scenario } else { createPlatformView(window, text, id); } + _nextFrame = _firstFrame; } @override void onBeginFrame(Duration duration) { - final SceneBuilder builder = SceneBuilder(); + _nextFrame(); + } - builder.pushOffset(0, 0); - finishBuilderByAddingPlatformViewAndPicture(builder, _viewId); + @override + void onDrawFrame() { + // Some iOS gesture recognizers bugs are introduced in the second frame (with a different platform view rect) after laying out the platform view. + // So in this test, we load 2 frames to ensure that we cover those cases. + // See https://github.com/flutter/flutter/issues/66044 + if (_nextFrame == _firstFrame) { + _nextFrame = _secondFrame; + window.scheduleFrame(); + } + super.onDrawFrame(); } @override @@ -585,6 +597,20 @@ class PlatformViewForTouchIOSScenario extends Scenario } } + + void _firstFrame() { + final SceneBuilder builder = SceneBuilder(); + + builder.pushOffset(0, 0); + finishBuilderByAddingPlatformViewAndPicture(builder, _viewId); + } + + void _secondFrame() { + final SceneBuilder builder = SceneBuilder(); + + builder.pushOffset(5, 5); + finishBuilderByAddingPlatformViewAndPicture(builder, _viewId); + } } mixin _BasePlatformViewScenarioMixin on Scenario {