-
Notifications
You must be signed in to change notification settings - Fork 6k
iOS platform view gesture blocking policy. #15940
Changes from 5 commits
53f0144
cda277d
c251e27
e9e8e90
c6bd198
96f28c0
9cf53c5
7b06bb3
ef6f470
839c3f7
705a105
4df622a
f262ca0
488f394
6f67a00
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
| */ | ||
| typedef enum { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typos: framework, policy, implemented,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: |
||
| * 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 = | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
@@ -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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: can expose a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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]); | ||
|
|
@@ -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) { | ||
|
|
@@ -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; | ||
|
|
@@ -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: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this the auto indentation?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
|
@@ -554,6 +570,7 @@ - (instancetype)initWithEmbeddedView:(UIView*)embeddedView | |
| initWithTarget:self | ||
| action:nil | ||
| forwardingRecognizer:forwardingRecognizer]); | ||
| _blockingPolicy = blockingPolicy; | ||
|
|
||
| [self addGestureRecognizer:_delayingRecognizer.get()]; | ||
| [self addGestureRecognizer:forwardingRecognizer]; | ||
|
|
@@ -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 | ||
|
|
@@ -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; | ||
|
|
@@ -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; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
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.