Skip to content

Commit

Permalink
Reland "Only reject gestures to embedded UIViews when the framework …
Browse files Browse the repository at this point in the history
…sa… (flutter#7315)

This re-lands commit cc9c670, with a few fixes:

  - Keep the DelayingGestureRecognizer a discrete gesture recognizer, when it was set to a began state embedded WkWebViews wasn't receiving touch events.
  - Fix a bug of not retaining the forwardRecognizer pointer when assigning it to a scoped_nsobject.
  • Loading branch information
amirh committed Dec 27, 2018
1 parent be69b07 commit 245317a
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 20 deletions.
66 changes: 46 additions & 20 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
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,43 @@ - (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 retain]);
}
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)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
self.state = UIGestureRecognizerStateRecognized;
self.state = UIGestureRecognizerStateFailed;
}
@end

Expand Down Expand Up @@ -380,12 +406,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 = UIGestureRecognizerStateFailed;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
Expand Down
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 245317a

Please sign in to comment.