Skip to content

Commit

Permalink
Only reject gestures to embedded UIViews when the framework says so. (f…
Browse files Browse the repository at this point in the history
…lutter#7307)

Previously the framework could only tell the engine to forward a touch
sequence to an embeded UIView between the time touches has started and
the time touches ended. This couldn't support gesture arena setups where
the gesture is recognized after the touch sequence is complete (e.g a
tap competing with a scroll).

This change makes it so that a touch gesture is only finally rejected by
a platform view when the framework invokes the `rejectGesture` method.
This allows the framework to resolve a gesture conflict after the touch
sequence was ended.
  • Loading branch information
amirh authored Dec 27, 2018
1 parent 2fb2b27 commit cc9c670
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 18 deletions.
66 changes: 48 additions & 18 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
OnDispose(call, result);
} else if ([[call method] isEqualToString:@"acceptGesture"]) {
OnAcceptGesture(call, result);
} else if ([[call method] isEqualToString:@"rejectGesture"]) {
OnRejectGesture(call, result);
} else {
result(FlutterMethodNotImplemented);
}
Expand Down Expand Up @@ -125,6 +127,24 @@
result(nil);
}

void FlutterPlatformViewsController::OnRejectGesture(FlutterMethodCall* call,
FlutterResult& result) {
NSDictionary<NSString*, id>* args = [call arguments];
int64_t viewId = [args[@"id"] longLongValue];

if (views_.count(viewId) == 0) {
result([FlutterError errorWithCode:@"unknown_view"
message:@"trying to set gesture state for an unknown view"
details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
return;
}

FlutterTouchInterceptingView* view = touch_interceptors_[viewId].get();
[view blockGesture];

result(nil);
}

void FlutterPlatformViewsController::RegisterViewFactory(
NSObject<FlutterPlatformViewFactory>* factory,
NSString* factoryId) {
Expand Down Expand Up @@ -269,6 +289,9 @@
// 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>
- (instancetype)initWithTarget:(id)target
action:(SEL)action
forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer;
@end

// While the DelayingGestureRecognizer is preventing touches from hitting the responder chain
Expand Down Expand Up @@ -301,7 +324,10 @@ - (instancetype)initWithEmbeddedView:(UIView*)embeddedView flutterView:(UIView*)
[[[ForwardingGestureRecognizer alloc] initWithTarget:self
flutterView:flutterView] autorelease];

_delayingRecognizer.reset([[DelayingGestureRecognizer alloc] initWithTarget:self action:nil]);
_delayingRecognizer.reset([[DelayingGestureRecognizer alloc]
initWithTarget:self
action:nil
forwardingRecognizer:forwardingRecognizer]);

[self addGestureRecognizer:_delayingRecognizer.get()];
[self addGestureRecognizer:forwardingRecognizer];
Expand All @@ -312,43 +338,47 @@ - (instancetype)initWithEmbeddedView:(UIView*)embeddedView flutterView:(UIView*)
- (void)releaseGesture {
_delayingRecognizer.get().state = UIGestureRecognizerStateFailed;
}

- (void)blockGesture {
_delayingRecognizer.get().state = UIGestureRecognizerStateEnded;
}

@end

@implementation DelayingGestureRecognizer
- (instancetype)initWithTarget:(id)target action:(SEL)action {
@implementation DelayingGestureRecognizer {
fml::scoped_nsobject<UIGestureRecognizer> _forwardingRecognizer;
}

- (instancetype)initWithTarget:(id)target
action:(SEL)action
forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer {
self = [super initWithTarget:target action:action];
if (self) {
self.delaysTouchesBegan = YES;
self.delegate = self;
_forwardingRecognizer.reset(forwardingRecognizer);
}
return self;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
return otherGestureRecognizer != self;
// The forwarding gesture recognizer should always get all touch events, so it should not be
// required to fail by any other gesture recognizer.
return otherGestureRecognizer != _forwardingRecognizer.get() && otherGestureRecognizer != self;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
return otherGestureRecognizer == self;
}

- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
// The gesture has ended, and the delaying gesture recognizer was not failed, we recognize
// the gesture to prevent the touches from being dispatched to the embedded view.
//
// This doesn't work well with gestures that are recognized by the Flutter framework after
// all pointers are up.
//
// TODO(amirh): explore if we can instead set this to recognized when the next touch sequence
// begins, or we can use a framework signal for restarting the recognizers (e.g when the
// gesture arena is resolved).
self.state = UIGestureRecognizerStateRecognized;
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
self.state = UIGestureRecognizerStateBegan;
}

- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
self.state = UIGestureRecognizerStateRecognized;
self.state = UIGestureRecognizerStateCancelled;
}
@end

Expand Down Expand Up @@ -380,12 +410,12 @@ - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {

- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
[_flutterView touchesEnded:touches withEvent:event];
self.state = UIGestureRecognizerStateRecognized;
self.state = UIGestureRecognizerStateEnded;
}

- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
[_flutterView touchesCancelled:touches withEvent:event];
self.state = UIGestureRecognizerStateRecognized;
self.state = UIGestureRecognizerStateCancelled;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@

// 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

namespace shell {
Expand Down Expand Up @@ -89,6 +92,7 @@ class FlutterPlatformViewsController {
void OnCreate(FlutterMethodCall* call, FlutterResult& result);
void OnDispose(FlutterMethodCall* call, FlutterResult& result);
void OnAcceptGesture(FlutterMethodCall* call, FlutterResult& result);
void OnRejectGesture(FlutterMethodCall* call, FlutterResult& result);

void EnsureOverlayInitialized(int64_t overlay_id);
void EnsureGLOverlayInitialized(int64_t overlay_id,
Expand Down

0 comments on commit cc9c670

Please sign in to comment.