Always set change type to cancel with touchesCancelled on iOS platform view (#24333)

This commit is contained in:
Chris Yang 2021-02-10 17:51:01 -08:00 committed by GitHub
parent 7e48c42679
commit b9ecd8aca6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 79 additions and 5 deletions

View File

@ -15,6 +15,7 @@
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
#import "flutter/shell/platform/darwin/ios/ios_surface.h"
#import "flutter/shell/platform/darwin/ios/ios_surface_gl.h"
@ -938,7 +939,12 @@ void FlutterPlatformViewsController::ResetFrameState() {
}
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
[_flutterViewController.get() touchesCancelled:touches withEvent:event];
// In the event of platform view is removed, iOS generates a "stationary" change type instead of
// "cancelled" change type.
// Flutter needs all the cancelled touches to be "cancelled" change types in order to correctly
// handle gesture sequence.
// We always override the change type to "cancelled".
[((FlutterViewController*)_flutterViewController.get()) forceTouchesCancelled:touches];
_currentTouchPointersCount -= touches.count;
if (_currentTouchPointersCount == 0) {
self.state = UIGestureRecognizerStateFailed;

View File

@ -626,7 +626,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
// Before setting flutter view controller, events are not dispatched.
NSSet* touches1 = [[[NSSet alloc] init] autorelease];
id event1 = OCMClassMock([UIEvent class]);
id mockFlutterViewContoller = OCMClassMock([UIViewController class]);
id mockFlutterViewContoller = OCMClassMock([FlutterViewController class]);
[forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
OCMReject([mockFlutterViewContoller touchesBegan:touches1 withEvent:event1]);
@ -684,7 +684,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
break;
}
}
id mockFlutterViewContoller = OCMClassMock([UIViewController class]);
id mockFlutterViewContoller = OCMClassMock([FlutterViewController class]);
{
// ***** Sequence 1, finishing touch event with touchEnded ***** //
flutterPlatformViewsController->SetFlutterViewController(mockFlutterViewContoller);
@ -739,7 +739,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
NSSet* touches3 = [[[NSSet alloc] init] autorelease];
id event3 = OCMClassMock([UIEvent class]);
[forwardGectureRecognizer touchesCancelled:touches3 withEvent:event3];
OCMVerify([mockFlutterViewContoller touchesCancelled:touches3 withEvent:event3]);
OCMVerify([mockFlutterViewContoller forceTouchesCancelled:touches3]);
// Now the 2nd touch sequence should not be allowed.
NSSet* touches4 = [[[NSSet alloc] init] autorelease];
@ -803,7 +803,7 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
break;
}
}
id mockFlutterViewContoller = OCMClassMock([UIViewController class]);
id mockFlutterViewContoller = OCMClassMock([FlutterViewController class]);
flutterPlatformViewsController->SetFlutterViewController(mockFlutterViewContoller);
@ -866,6 +866,66 @@ fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
flutterPlatformViewsController->Reset();
}
- (void)testFlutterPlatformViewTouchesCancelledEventAreForcedToBeCancelled {
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
/*platform=*/thread_task_runner,
/*raster=*/thread_task_runner,
/*ui=*/thread_task_runner,
/*io=*/thread_task_runner);
auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
/*delegate=*/mock_delegate,
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
/*platform_views_controller=*/flutterPlatformViewsController,
/*task_runners=*/runners);
FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
[[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
flutterPlatformViewsController->RegisterViewFactory(
factory, @"MockFlutterPlatformView",
FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
FlutterResult result = ^(id result) {
};
flutterPlatformViewsController->OnMethodCall(
[FlutterMethodCall
methodCallWithMethodName:@"create"
arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
result);
XCTAssertNotNil(gMockPlatformView);
// Find touch inteceptor view
UIView* touchInteceptorView = gMockPlatformView;
while (touchInteceptorView != nil &&
![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
touchInteceptorView = touchInteceptorView.superview;
}
XCTAssertNotNil(touchInteceptorView);
// Find ForwardGestureRecognizer
UIGestureRecognizer* forwardGectureRecognizer = nil;
for (UIGestureRecognizer* gestureRecognizer in touchInteceptorView.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:NSClassFromString(@"ForwardingGestureRecognizer")]) {
forwardGectureRecognizer = gestureRecognizer;
break;
}
}
id mockFlutterViewContoller = OCMClassMock([FlutterViewController class]);
flutterPlatformViewsController->SetFlutterViewController(mockFlutterViewContoller);
NSSet* touches1 = [NSSet setWithObject:@1];
id event1 = OCMClassMock([UIEvent class]);
[forwardGectureRecognizer touchesBegan:touches1 withEvent:event1];
[forwardGectureRecognizer touchesCancelled:touches1 withEvent:event1];
OCMVerify([mockFlutterViewContoller forceTouchesCancelled:touches1]);
flutterPlatformViewsController->Reset();
}
- (void)testFlutterPlatformViewControllerSubmitFrameWithoutFlutterViewNotCrashing {
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");

View File

@ -947,6 +947,11 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
[self dispatchTouches:touches pointerDataChangeOverride:nullptr];
}
- (void)forceTouchesCancelled:(NSSet*)touches {
flutter::PointerData::Change cancel = flutter::PointerData::Change::kCancel;
[self dispatchTouches:touches pointerDataChangeOverride:&cancel];
}
#pragma mark - Handle view resizing
- (void)updateViewportMetrics {

View File

@ -29,6 +29,9 @@ extern NSNotificationName const FlutterViewControllerShowHomeIndicator;
- (fml::WeakPtr<FlutterViewController>)getWeakPtr;
- (std::shared_ptr<flutter::FlutterPlatformViewsController>&)platformViewsController;
- (FlutterRestorationPlugin*)restorationPlugin;
// Send touches to the Flutter Engine while forcing the change type to be cancelled.
// The `phase`s in `touches` are ignored.
- (void)forceTouchesCancelled:(NSSet*)touches;
@end