Skip to content
This repository has been archived by the owner on Feb 25, 2025. It is now read-only.

Commit

Permalink
The ForwardingGestureRecognizers to have back reference to the `Pla…
Browse files Browse the repository at this point in the history
…tformViewsController` so it can access `FlutterViewController` when its available (#20708)
  • Loading branch information
Chris Yang authored Aug 24, 2020
1 parent 9939c26 commit 1c13aba
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 38 deletions.
48 changes: 29 additions & 19 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -701,15 +705,17 @@ - (instancetype)initWithTarget:(id)target
// directly to the FlutterView.
@interface ForwardingGestureRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>
- (instancetype)initWithTarget:(id)target
flutterViewController:(UIViewController*)flutterViewController;
platformViewsController:
(fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController;
@end

@implementation FlutterTouchInterceptingView {
fml::scoped_nsobject<DelayingGestureRecognizer> _delayingRecognizer;
FlutterPlatformViewGestureRecognizersBlockingPolicy _blockingPolicy;
}
- (instancetype)initWithEmbeddedView:(UIView*)embeddedView
flutterViewController:(UIViewController*)flutterViewController
platformViewsController:
(fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController
gestureRecognizersBlockingPolicy:
(FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy {
self = [super initWithFrame:embeddedView.frame];
Expand All @@ -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
Expand Down Expand Up @@ -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<flutter::FlutterPlatformViewsController> _platformViewsController;
// Counting the pointers that has started in one touch sequence.
NSInteger _currentTouchPointersCount;
}

- (instancetype)initWithTarget:(id)target
flutterViewController:(UIViewController*)flutterViewController {
platformViewsController:
(fml::WeakPtr<flutter::FlutterPlatformViewsController>)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
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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<flutter::PlatformViewIOS>(
/*delegate=*/mock_delegate,
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
/*task_runners=*/runners);

auto flutterPlatformViewsController = std::make_unique<flutter::FlutterPlatformViewsController>();

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};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -143,10 +127,14 @@ class FlutterPlatformViewsController {

~FlutterPlatformViewsController();

fml::WeakPtr<flutter::FlutterPlatformViewsController> GetWeakPtr();

void SetFlutterView(UIView* flutter_view);

void SetFlutterViewController(UIViewController* flutter_view_controller);

UIViewController* getFlutterViewController();

void RegisterViewFactory(
NSObject<FlutterPlatformViewFactory>* factory,
NSString* factoryId,
Expand Down Expand Up @@ -255,6 +243,8 @@ class FlutterPlatformViewsController {
std::map<std::string, FlutterPlatformViewGestureRecognizersBlockingPolicy>
gesture_recognizers_blocking_policies;

std::unique_ptr<fml::WeakPtrFactory<FlutterPlatformViewsController>> weak_factory_;

void OnCreate(FlutterMethodCall* call, FlutterResult& result);
void OnDispose(FlutterMethodCall* call, FlutterResult& result);
void OnAcceptGesture(FlutterMethodCall* call, FlutterResult& result);
Expand Down Expand Up @@ -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<flutter::FlutterPlatformViewsController>)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_
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@
FlutterPlatformViewLayer::~FlutterPlatformViewLayer() = default;

FlutterPlatformViewsController::FlutterPlatformViewsController()
: layer_pool_(std::make_unique<FlutterPlatformViewLayerPool>()){};
: layer_pool_(std::make_unique<FlutterPlatformViewLayerPool>()),
weak_factory_(std::make_unique<fml::WeakPtrFactory<FlutterPlatformViewsController>>(this)){};

FlutterPlatformViewsController::~FlutterPlatformViewsController() = default;

fml::WeakPtr<flutter::FlutterPlatformViewsController> 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;
Expand Down

1 comment on commit 1c13aba

@nwparker
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit appears to have introduced a bug. Please see flutter/flutter#66097

Please sign in to comment.