Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,42 @@ typedef void (*FlutterPluginRegistrantCallback)(NSObject<FlutterPluginRegistry>*
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
@end

#pragma mark -
/***************************************************************************************************
* How the UIGestureRecognizers of a platform view should be blocked.
*
* UIGestureRecognizers of a platform views can be blocked based on the decisions made by the
* Flutter Framework. e.g. When an interact-able widget is covering the platform view and a gesture
* happened on the widget, The framework may decide to block the UIGestureRecognizers that are
* recognized (via the gesture recognized acton ) on the platform view if any.
*
* This policy describes how the blocking process is implemented.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: This policy determines how Flutter blocks a platform view's UIGestureRecognizers.

*/
typedef enum {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

typos: framework, policy, implemented,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

/**
* The flutter framework blocks all the UIGestureRecognizers on the platform view as soon as it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: s/flutter framework/Flutter/

* decides they should be blocked.
*
* If this policy is implemented, only touchesBegan for all the UIGestureRecognizers is guaranteed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: s/If this policy is implemented/With this policy/

* to be called.
*/
FlutterPlatformViewGestureRecognizersBlockingPolicyEager,
/**
* The flutter framework blocks all the UIGestureRecognizers until the `touchesEnded` method is

@cyanglaz cyanglaz Jan 27, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Actually I was wrong. The behavior is neither block...until nor block...after.
The gesture recognizers are blocked, but the touchesBegan and touchesEnded callbacks are not. I think we might need to be more implicit here.
What do you think about:

The flutter framework waits until the `touchesEneded` method is called
for every UIGestureRecognizer on the platform view before blocking
the "actions" of all the UIGestureRecognizers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I actually find your original comment easier to process 😄

Good point about emphasizing that what's being delayed are the actions. Another note is that it's not the "flutter framework" which is blocking the gesture.

How about something like:

Flutter blocks the platform view's UIGestureRecognizers from recognizing only after touchesEnded was invoked. This results in the platform view's UIGestureRecognizers seeing the entire touch sequence, but never recognizing the gesture (and never invoking actions).

* called for every UIGestureRecognizers on the platform view.
*
* If this policy is implemented, all of the `touchesBegan`, `touchesMoved`, `touchesEnded` and
* `touchesCancelled` on any UIGestureRecognizers are guaranteed to be called if iOS system
* decided to call them.
*/
FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded,
/**
* The default behavior is currently set to `FlutterPlatformViewGestureBlockingPolicyEager`
*/
FlutterPlatformViewGestureRecognizersBlockingPolicyDefault =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What's the benefit of having this as part of the public API vs. requiring plugins to be explicit? (or to imply default by using the register method that doesn't take a policy?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I thought it is easier for us to track what is the default behavior in the feature. Also it is easier to update when we want to switch default behaviors.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In this case I wouldn't expand a public API to make our implementation a little cleaner.

FlutterPlatformViewGestureRecognizersBlockingPolicyEager,
} FlutterPlatformViewGestureRecognizersBlockingPolicy;

#pragma mark -
/***************************************************************************************************
*Registration context for a single `FlutterPlugin`, providing a one stop shop
Expand Down Expand Up @@ -264,6 +300,23 @@ typedef void (*FlutterPluginRegistrantCallback)(NSObject<FlutterPluginRegistry>*
- (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
withId:(NSString*)factoryId;

/**
* Registers a `FlutterPlatformViewFactory` for creation of platform views.
*
* Plugins expose `UIView` for embedding in Flutter apps by registering a view factory.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: can expose a UIView

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is copied from the method above, should I apply the same fix there in this PR too?

*
* @param factory The view factory that will be registered.
* @param factoryId A unique identifier for the factory, the Dart code of the Flutter app can use
* this identifier to request creation of a `UIView` by the registered factory.
* @param gestureBlockingPolicy How UIGestureRecognizers on the platform views are
* blocked.
*
*/
- (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
withId:(NSString*)factoryId
gestureRecognizersBlockingPolicy:
(FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizersBlockingPolicy;

/**
* Publishes a value for external use of the plugin.
*
Expand Down
12 changes: 11 additions & 1 deletion shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,17 @@ - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {

- (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
withId:(NSString*)factoryId {
[_flutterEngine platformViewsController] -> RegisterViewFactory(factory, factoryId);
[self registerViewFactory:factory
withId:factoryId
gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyDefault];
}

- (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
withId:(NSString*)factoryId
gestureRecognizersBlockingPolicy:
(FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizersBlockingPolicy {
[_flutterEngine platformViewsController] -> RegisterViewFactory(factory, factoryId,
gestureRecognizersBlockingPolicy);
}

@end
65 changes: 60 additions & 5 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,10 @@
views_[viewId] = fml::scoped_nsobject<NSObject<FlutterPlatformView>>([embedded_view retain]);

FlutterTouchInterceptingView* touch_interceptor = [[[FlutterTouchInterceptingView alloc]
initWithEmbeddedView:embedded_view.view
flutterViewController:flutter_view_controller_.get()] autorelease];
initWithEmbeddedView:embedded_view.view
flutterViewController:flutter_view_controller_.get()
gestureRecognizersBlockingPolicy:gesture_recognizers_blocking_policies[viewType]]
autorelease];

touch_interceptors_[viewId] =
fml::scoped_nsobject<FlutterTouchInterceptingView>([touch_interceptor retain]);
Expand Down Expand Up @@ -149,11 +151,13 @@

void FlutterPlatformViewsController::RegisterViewFactory(
NSObject<FlutterPlatformViewFactory>* factory,
NSString* factoryId) {
NSString* factoryId,
FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy) {
std::string idString([factoryId UTF8String]);
FML_CHECK(factories_.count(idString) == 0);
factories_[idString] =
fml::scoped_nsobject<NSObject<FlutterPlatformViewFactory>>([factory retain]);
gesture_recognizers_blocking_policies[idString] = gestureRecognizerBlockingPolicy;
}

void FlutterPlatformViewsController::SetFrameSize(SkISize frame_size) {
Expand Down Expand Up @@ -513,6 +517,15 @@
// invoking an acceptGesture method on the platform_views channel). And this is how we allow the
// Flutter framework to delay or prevent the embedded view from getting a touch sequence.
@interface DelayingGestureRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>

// Indicates that if the `DelayingGestureRecognizer`'s state should be set to
// `UIGestureRecognizerStateEnded` during next `touchesEnded` call.
@property(nonatomic) bool shouldEndInNextTouchesEnded;

// Indicates that the `DelayingGestureRecognizer`'s `touchesEnded` has been invoked without
// setting the state to `UIGestureRecognizerStateEnded`.
@property(nonatomic) bool touchedEndedWithoutBlocking;

- (instancetype)initWithTarget:(id)target
action:(SEL)action
forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer;
Expand All @@ -535,9 +548,12 @@ - (instancetype)initWithTarget:(id)target

@implementation FlutterTouchInterceptingView {
fml::scoped_nsobject<DelayingGestureRecognizer> _delayingRecognizer;
FlutterPlatformViewGestureRecognizersBlockingPolicy _blockingPolicy;
}
- (instancetype)initWithEmbeddedView:(UIView*)embeddedView
flutterViewController:(UIViewController*)flutterViewController {
flutterViewController:(UIViewController*)flutterViewController
gestureRecognizersBlockingPolicy:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is this the auto indentation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, indented by the formatter

(FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy {
self = [super initWithFrame:embeddedView.frame];
if (self) {
self.multipleTouchEnabled = YES;
Expand All @@ -554,6 +570,7 @@ - (instancetype)initWithEmbeddedView:(UIView*)embeddedView
initWithTarget:self
action:nil
forwardingRecognizer:forwardingRecognizer]);
_blockingPolicy = blockingPolicy;

[self addGestureRecognizer:_delayingRecognizer.get()];
[self addGestureRecognizer:forwardingRecognizer];
Expand All @@ -566,7 +583,27 @@ - (void)releaseGesture {
}

- (void)blockGesture {
_delayingRecognizer.get().state = UIGestureRecognizerStateEnded;
switch (_blockingPolicy) {
case FlutterPlatformViewGestureRecognizersBlockingPolicyEager:
// We block all other gesture recognizers immediately in this policy.
_delayingRecognizer.get().state = UIGestureRecognizerStateEnded;
break;
case FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded:
if (_delayingRecognizer.get().touchedEndedWithoutBlocking) {
// If touchesEnded of the `DelayingGesureRecognizer` has been already invoked,
// we want to set the state of the `DelayingGesureRecognizer` to
// `UIGestureRecognizerStateEnded` as soon as possible.
_delayingRecognizer.get().state = UIGestureRecognizerStateEnded;
} else {
// If touchesEnded of the `DelayingGesureRecognizer` has not been invoked,
// We will set a flag to notify the `DelayingGesureRecognizer` to set the state to
// `UIGestureRecognizerStateEnded` when touchesEnded is called.
_delayingRecognizer.get().shouldEndInNextTouchesEnded = YES;
}
break;
default:
break;
}
}

// We want the intercepting view to consume the touches and not pass the touches up to the parent
Expand Down Expand Up @@ -596,7 +633,10 @@ - (instancetype)initWithTarget:(id)target
self = [super initWithTarget:target action:action];
if (self) {
self.delaysTouchesBegan = YES;
self.delaysTouchesEnded = YES;
self.delegate = self;
self.shouldEndInNextTouchesEnded = NO;
self.touchedEndedWithoutBlocking = NO;
_forwardingRecognizer.reset([forwardingRecognizer retain]);
}
return self;
Expand All @@ -614,6 +654,21 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
return otherGestureRecognizer == self;
}

- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
self.touchedEndedWithoutBlocking = NO;
[super touchesBegan:touches withEvent:event];
}

- (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
if (self.shouldEndInNextTouchesEnded) {
self.state = UIGestureRecognizerStateEnded;
self.shouldEndInNextTouchesEnded = NO;
} else {
self.touchedEndedWithoutBlocking = YES;
}
[super touchesEnded:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
self.state = UIGestureRecognizerStateFailed;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h"
#include "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h"
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h"

// A UIView that is used as the parent for embedded UIViews.
//
Expand All @@ -19,7 +20,9 @@
// 2. Dispatching all events that are hittested to the embedded view to the FlutterView.
@interface FlutterTouchInterceptingView : UIView
- (instancetype)initWithEmbeddedView:(UIView*)embeddedView
flutterViewController:(UIViewController*)flutterViewController;
flutterViewController:(UIViewController*)flutterViewController
gestureRecognizersBlockingPolicy:
(FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy;

// Stop delaying any active touch sequence (and let it arrive the embedded view).
- (void)releaseGesture;
Expand Down Expand Up @@ -80,7 +83,10 @@ class FlutterPlatformViewsController {

void SetFlutterViewController(UIViewController* flutter_view_controller);

void RegisterViewFactory(NSObject<FlutterPlatformViewFactory>* factory, NSString* factoryId);
void RegisterViewFactory(
NSObject<FlutterPlatformViewFactory>* factory,
NSString* factoryId,
FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy);

void SetFrameSize(SkISize frame_size);

Expand Down Expand Up @@ -152,6 +158,10 @@ class FlutterPlatformViewsController {
// Only compoiste platform views in this set.
std::unordered_set<int64_t> views_to_recomposite_;

// The FlutterPlatformViewGestureRecognizersBlockingPolicy for each type of platform view.
std::map<std::string, FlutterPlatformViewGestureRecognizersBlockingPolicy>
gesture_recognizers_blocking_policies;

std::map<int64_t, std::unique_ptr<SkPictureRecorder>> picture_recorders_;

void OnCreate(FlutterMethodCall* call, FlutterResult& result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,4 @@ - (UIView*)view {
return _textView;
}

// - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
// }

@end