diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index e49e0fa78cdb5..fdfcd6f85fd2e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -107,6 +107,10 @@ flutter_view_controller_.reset([flutter_view_controller retain]); } +UIViewController* FlutterPlatformViewsController::getFlutterViewController() { + return flutter_view_controller_.get(); +} + void FlutterPlatformViewsController::OnMethodCall(FlutterMethodCall* call, FlutterResult& result) { if ([[call method] isEqualToString:@"create"]) { OnCreate(call, result); @@ -161,7 +165,7 @@ FlutterTouchInterceptingView* touch_interceptor = [[[FlutterTouchInterceptingView alloc] initWithEmbeddedView:embedded_view.view - flutterViewController:flutter_view_controller_.get() + platformViewsController:GetWeakPtr() gestureRecognizersBlockingPolicy:gesture_recognizers_blocking_policies[viewType]] autorelease]; @@ -701,7 +705,8 @@ - (instancetype)initWithTarget:(id)target // directly to the FlutterView. @interface ForwardingGestureRecognizer : UIGestureRecognizer - (instancetype)initWithTarget:(id)target - flutterViewController:(UIViewController*)flutterViewController; + platformViewsController: + (fml::WeakPtr)platformViewsController; @end @implementation FlutterTouchInterceptingView { @@ -709,7 +714,8 @@ @implementation FlutterTouchInterceptingView { FlutterPlatformViewGestureRecognizersBlockingPolicy _blockingPolicy; } - (instancetype)initWithEmbeddedView:(UIView*)embeddedView - flutterViewController:(UIViewController*)flutterViewController + platformViewsController: + (fml::WeakPtr)platformViewsController gestureRecognizersBlockingPolicy: (FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy { self = [super initWithFrame:embeddedView.frame]; @@ -720,9 +726,9 @@ - (instancetype)initWithEmbeddedView:(UIView*)embeddedView [self addSubview:embeddedView]; - ForwardingGestureRecognizer* forwardingRecognizer = - [[[ForwardingGestureRecognizer alloc] initWithTarget:self - flutterViewController:flutterViewController] autorelease]; + ForwardingGestureRecognizer* forwardingRecognizer = [[[ForwardingGestureRecognizer alloc] + initWithTarget:self + platformViewsController:std::move(platformViewsController)] autorelease]; _delayingRecognizer.reset([[DelayingGestureRecognizer alloc] initWithTarget:self @@ -833,39 +839,42 @@ - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { @end @implementation ForwardingGestureRecognizer { - // We can't dispatch events to the framework without this back pointer. - // This is a weak reference, the ForwardingGestureRecognizer is owned by the - // FlutterTouchInterceptingView which is strong referenced only by the FlutterView, - // which is strongly referenced by the FlutterViewController. - // So this is safe as when FlutterView is deallocated the reference to ForwardingGestureRecognizer - // will go away. - UIViewController* _flutterViewController; + // Weak reference to FlutterPlatformViewsController. The FlutterPlatformViewsController has + // a reference to the FlutterViewController, where we can dispatch pointer events to. + // + // The lifecycle of FlutterPlatformViewsController is bind to FlutterEngine, which should always + // outlives the FlutterViewController. And ForwardingGestureRecognizer is owned by a subview of + // FlutterView, so the ForwardingGestureRecognizer never out lives FlutterViewController. + // Therefore, `_platformViewsController` should never be nullptr. + fml::WeakPtr _platformViewsController; // Counting the pointers that has started in one touch sequence. NSInteger _currentTouchPointersCount; } - (instancetype)initWithTarget:(id)target - flutterViewController:(UIViewController*)flutterViewController { + platformViewsController: + (fml::WeakPtr)platformViewsController { self = [super initWithTarget:target action:nil]; if (self) { self.delegate = self; - _flutterViewController = flutterViewController; + FML_DCHECK(platformViewsController.get() != nullptr); + _platformViewsController = std::move(platformViewsController); _currentTouchPointersCount = 0; } return self; } - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { - [_flutterViewController touchesBegan:touches withEvent:event]; + [_platformViewsController.get()->getFlutterViewController() touchesBegan:touches withEvent:event]; _currentTouchPointersCount += touches.count; } - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { - [_flutterViewController touchesMoved:touches withEvent:event]; + [_platformViewsController.get()->getFlutterViewController() touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { - [_flutterViewController touchesEnded:touches withEvent:event]; + [_platformViewsController.get()->getFlutterViewController() touchesEnded:touches withEvent:event]; _currentTouchPointersCount -= touches.count; // Touches in one touch sequence are sent to the touchesEnded method separately if different // fingers stop touching the screen at different time. So one touchesEnded method triggering does @@ -877,7 +886,8 @@ - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { } - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { - [_flutterViewController touchesCancelled:touches withEvent:event]; + [_platformViewsController.get()->getFlutterViewController() touchesCancelled:touches + withEvent:event]; _currentTouchPointersCount = 0; self.state = UIGestureRecognizerStateFailed; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index 0e8397e1146b4..39d6b987dc4ca 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -7,7 +7,9 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.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/platform_view_ios.h" #import "third_party/ocmock/Source/OCMock/OCMock.h" @@ -508,6 +510,70 @@ - (void)testClipPath { flutterPlatformViewsController->Reset(); } +- (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents { + 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 platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + /*task_runners=*/runners); + + auto flutterPlatformViewsController = std::make_unique(); + + 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; + } + } + + // Before setting flutter view controller, events are not dispatched. + NSSet* touches1 = OCMClassMock([NSSet class]); + UIEvent* event1 = OCMClassMock([UIEvent class]); + UIViewController* mockFlutterViewContoller = OCMClassMock([UIViewController class]); + [forwardGectureRecognizer touchesBegan:touches1 withEvent:event1]; + OCMReject([mockFlutterViewContoller touchesBegan:touches1 withEvent:event1]); + + // Set flutter view controller allows events to be dispatched. + NSSet* touches2 = OCMClassMock([NSSet class]); + UIEvent* event2 = OCMClassMock([UIEvent class]); + flutterPlatformViewsController->SetFlutterViewController(mockFlutterViewContoller); + [forwardGectureRecognizer touchesBegan:touches2 withEvent:event2]; + OCMVerify([mockFlutterViewContoller touchesBegan:touches2 withEvent:event2]); + + flutterPlatformViewsController->Reset(); +} + - (int)alphaOfPoint:(CGPoint)point onView:(UIView*)view { unsigned char pixel[4] = {0}; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 796d1e5d14bcc..2364da27563a9 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -16,6 +16,8 @@ #include "flutter/shell/platform/darwin/ios/ios_context.h" #include "third_party/skia/include/core/SkPictureRecorder.h" +@class FlutterTouchInterceptingView; + // A UIView that acts as a clipping mask for the |ChildClippingView|. // // On the [UIView drawRect:] method, this view performs a series of clipping operations and sets the @@ -43,24 +45,6 @@ @end -// A UIView that is used as the parent for embedded UIViews. -// -// This view has 2 roles: -// 1. Delay or prevent touch events from arriving the embedded view. -// 2. Dispatching all events that are hittested to the embedded view to the FlutterView. -@interface FlutterTouchInterceptingView : UIView -- (instancetype)initWithEmbeddedView:(UIView*)embeddedView - flutterViewController:(UIViewController*)flutterViewController - gestureRecognizersBlockingPolicy: - (FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy; - -// Stop delaying any active touch sequence (and let it arrive the embedded view). -- (void)releaseGesture; - -// Prevent the touch sequence from ever arriving to the embedded view. -- (void)blockGesture; -@end - // The parent view handles clipping to its subviews. @interface ChildClippingView : UIView @@ -143,10 +127,14 @@ class FlutterPlatformViewsController { ~FlutterPlatformViewsController(); + fml::WeakPtr GetWeakPtr(); + void SetFlutterView(UIView* flutter_view); void SetFlutterViewController(UIViewController* flutter_view_controller); + UIViewController* getFlutterViewController(); + void RegisterViewFactory( NSObject* factory, NSString* factoryId, @@ -255,6 +243,8 @@ class FlutterPlatformViewsController { std::map gesture_recognizers_blocking_policies; + std::unique_ptr> weak_factory_; + void OnCreate(FlutterMethodCall* call, FlutterResult& result); void OnDispose(FlutterMethodCall* call, FlutterResult& result); void OnAcceptGesture(FlutterMethodCall* call, FlutterResult& result); @@ -313,4 +303,23 @@ class FlutterPlatformViewsController { } // namespace flutter +// A UIView that is used as the parent for embedded UIViews. +// +// This view has 2 roles: +// 1. Delay or prevent touch events from arriving the embedded view. +// 2. Dispatching all events that are hittested to the embedded view to the FlutterView. +@interface FlutterTouchInterceptingView : UIView +- (instancetype)initWithEmbeddedView:(UIView*)embeddedView + platformViewsController: + (fml::WeakPtr)platformViewsController + gestureRecognizersBlockingPolicy: + (FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy; + +// Stop delaying any active touch sequence (and let it arrive the embedded view). +- (void)releaseGesture; + +// Prevent the touch sequence from ever arriving to the embedded view. +- (void)blockGesture; +@end + #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMVIEWS_INTERNAL_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm index 5e9ed80279975..0179debf2c5a4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -24,10 +24,15 @@ FlutterPlatformViewLayer::~FlutterPlatformViewLayer() = default; FlutterPlatformViewsController::FlutterPlatformViewsController() - : layer_pool_(std::make_unique()){}; + : layer_pool_(std::make_unique()), + weak_factory_(std::make_unique>(this)){}; FlutterPlatformViewsController::~FlutterPlatformViewsController() = default; +fml::WeakPtr FlutterPlatformViewsController::GetWeakPtr() { + return weak_factory_->GetWeakPtr(); +} + CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix) { // Skia only supports 2D transform so we don't map z. CATransform3D transform = CATransform3DIdentity;