• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import <UIKit/UIGestureRecognizerSubclass.h>
6
7#import "FlutterOverlayView.h"
8#import "flutter/shell/platform/darwin/ios/ios_surface.h"
9#import "flutter/shell/platform/darwin/ios/ios_surface_gl.h"
10
11#include <map>
12#include <memory>
13#include <string>
14
15#include "FlutterPlatformViews_Internal.h"
16#include "flutter/fml/platform/darwin/scoped_nsobject.h"
17#include "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
18
19namespace flutter {
20
21void FlutterPlatformViewsController::SetFlutterView(UIView* flutter_view) {
22  flutter_view_.reset([flutter_view retain]);
23}
24
25void FlutterPlatformViewsController::SetFlutterViewController(
26    UIViewController* flutter_view_controller) {
27  flutter_view_controller_.reset([flutter_view_controller retain]);
28}
29
30void FlutterPlatformViewsController::OnMethodCall(FlutterMethodCall* call, FlutterResult& result) {
31  if ([[call method] isEqualToString:@"create"]) {
32    OnCreate(call, result);
33  } else if ([[call method] isEqualToString:@"dispose"]) {
34    OnDispose(call, result);
35  } else if ([[call method] isEqualToString:@"acceptGesture"]) {
36    OnAcceptGesture(call, result);
37  } else if ([[call method] isEqualToString:@"rejectGesture"]) {
38    OnRejectGesture(call, result);
39  } else {
40  }
41}
42
43void FlutterPlatformViewsController::OnCreate(FlutterMethodCall* call, FlutterResult& result) {
44  if (!flutter_view_.get()) {
45    return;
46  }
47  NSDictionary<NSString*, id>* args = [call arguments];
48
49  long viewId = [args[@"id"] longValue];
50  std::string viewType([args[@"viewType"] UTF8String]);
51
52  NSObject<FlutterPlatformViewFactory>* factory = factories_[viewType].get();
53  if (factory == nil) {
54    return;
55  }
56
57  id params = nil;
58  if ([factory respondsToSelector:@selector(createArgsCodec)]) {
59    NSObject<FlutterMessageCodec>* codec = [factory createArgsCodec];
60    if (codec != nil && args[@"params"] != nil) {
61      FlutterStandardTypedData* paramsData = args[@"params"];
62      params = [codec decode:paramsData.data];
63    }
64  }
65
66  NSObject<FlutterPlatformView>* embedded_view = [factory createWithFrame:CGRectZero
67                                                           viewIdentifier:viewId
68                                                                arguments:params];
69  views_[viewId] = fml::scoped_nsobject<NSObject<FlutterPlatformView>>([embedded_view retain]);
70
71  FlutterTouchInterceptingView* touch_interceptor = [[[FlutterTouchInterceptingView alloc]
72       initWithEmbeddedView:embedded_view.view
73      flutterViewController:flutter_view_controller_.get()] autorelease];
74
75  touch_interceptors_[viewId] =
76      fml::scoped_nsobject<FlutterTouchInterceptingView>([touch_interceptor retain]);
77  root_views_[viewId] = fml::scoped_nsobject<UIView>([touch_interceptor retain]);
78
79  result(nil);
80}
81
82void FlutterPlatformViewsController::OnDispose(FlutterMethodCall* call, FlutterResult& result) {
83  NSNumber* arg = [call arguments];
84  int64_t viewId = [arg longLongValue];
85
86  if (views_.count(viewId) == 0) {
87    return;
88  }
89  // We wait for next submitFrame to dispose views.
90  views_to_dispose_.insert(viewId);
91  result(nil);
92}
93
94void FlutterPlatformViewsController::OnAcceptGesture(FlutterMethodCall* call,
95                                                     FlutterResult& result) {
96  NSDictionary<NSString*, id>* args = [call arguments];
97  int64_t viewId = [args[@"id"] longLongValue];
98
99  if (views_.count(viewId) == 0) {
100    return;
101  }
102
103  FlutterTouchInterceptingView* view = touch_interceptors_[viewId].get();
104  [view releaseGesture];
105
106  result(nil);
107}
108
109void FlutterPlatformViewsController::OnRejectGesture(FlutterMethodCall* call,
110                                                     FlutterResult& result) {
111  NSDictionary<NSString*, id>* args = [call arguments];
112  int64_t viewId = [args[@"id"] longLongValue];
113
114  if (views_.count(viewId) == 0) {
115    return;
116  }
117
118  FlutterTouchInterceptingView* view = touch_interceptors_[viewId].get();
119  [view blockGesture];
120
121  result(nil);
122}
123
124void FlutterPlatformViewsController::RegisterViewFactory(
125    NSObject<FlutterPlatformViewFactory>* factory,
126    NSString* factoryId) {
127  std::string idString([factoryId UTF8String]);
128  FML_CHECK(factories_.count(idString) == 0);
129  factories_[idString] =
130      fml::scoped_nsobject<NSObject<FlutterPlatformViewFactory>>([factory retain]);
131}
132
133void FlutterPlatformViewsController::SetFrameSize(SkISize frame_size) {
134  frame_size_ = frame_size;
135}
136
137void FlutterPlatformViewsController::CancelFrame() {
138  composition_order_.clear();
139}
140
141bool FlutterPlatformViewsController::HasPendingViewOperations() {
142  if (!views_to_recomposite_.empty()) {
143    return true;
144  }
145  return active_composition_order_ != composition_order_;
146}
147
148const int FlutterPlatformViewsController::kDefaultMergedLeaseDuration;
149
150PostPrerollResult FlutterPlatformViewsController::PostPrerollAction(
151    fml::RefPtr<fml::GpuThreadMerger> gpu_thread_merger) {
152  const bool uiviews_mutated = HasPendingViewOperations();
153  if (uiviews_mutated) {
154    if (gpu_thread_merger->IsMerged()) {
155      gpu_thread_merger->ExtendLeaseTo(kDefaultMergedLeaseDuration);
156    } else {
157      CancelFrame();
158      gpu_thread_merger->MergeWithLease(kDefaultMergedLeaseDuration);
159      return PostPrerollResult::kResubmitFrame;
160    }
161  }
162  return PostPrerollResult::kSuccess;
163}
164
165void FlutterPlatformViewsController::PrerollCompositeEmbeddedView(
166    int view_id,
167    std::unique_ptr<EmbeddedViewParams> params) {
168  picture_recorders_[view_id] = std::make_unique<SkPictureRecorder>();
169  picture_recorders_[view_id]->beginRecording(SkRect::Make(frame_size_));
170  picture_recorders_[view_id]->getRecordingCanvas()->clear(SK_ColorTRANSPARENT);
171  composition_order_.push_back(view_id);
172
173  if (current_composition_params_.count(view_id) == 1 &&
174      current_composition_params_[view_id] == *params.get()) {
175    // Do nothing if the params didn't change.
176    return;
177  }
178  current_composition_params_[view_id] = EmbeddedViewParams(*params.get());
179  views_to_recomposite_.insert(view_id);
180}
181
182NSObject<FlutterPlatformView>* FlutterPlatformViewsController::GetPlatformViewByID(int view_id) {
183  if (views_.empty()) {
184    return nil;
185  }
186  return views_[view_id].get();
187}
188
189std::vector<SkCanvas*> FlutterPlatformViewsController::GetCurrentCanvases() {
190  std::vector<SkCanvas*> canvases;
191  for (size_t i = 0; i < composition_order_.size(); i++) {
192    int64_t view_id = composition_order_[i];
193    canvases.push_back(picture_recorders_[view_id]->getRecordingCanvas());
194  }
195  return canvases;
196}
197
198int FlutterPlatformViewsController::CountClips(const MutatorsStack& mutators_stack) {
199  std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator iter = mutators_stack.Bottom();
200  int clipCount = 0;
201  while (iter != mutators_stack.Top()) {
202    if ((*iter)->IsClipType()) {
203      clipCount++;
204    }
205    ++iter;
206  }
207  return clipCount;
208}
209
210UIView* FlutterPlatformViewsController::ReconstructClipViewsChain(int number_of_clips,
211                                                                  UIView* platform_view,
212                                                                  UIView* head_clip_view) {
213  NSInteger indexInFlutterView = -1;
214  if (head_clip_view.superview) {
215    // TODO(cyanglaz): potentially cache the index of oldPlatformViewRoot to make this a O(1).
216    // https://github.com/flutter/flutter/issues/35023
217    indexInFlutterView = [flutter_view_.get().subviews indexOfObject:head_clip_view];
218    [head_clip_view removeFromSuperview];
219  }
220  UIView* head = platform_view;
221  int clipIndex = 0;
222  // Re-use as much existing clip views as needed.
223  while (head != head_clip_view && clipIndex < number_of_clips) {
224    head = head.superview;
225    clipIndex++;
226  }
227  // If there were not enough existing clip views, add more.
228  while (clipIndex < number_of_clips) {
229    ChildClippingView* clippingView = [ChildClippingView new];
230    [clippingView addSubview:head];
231    head = clippingView;
232    clipIndex++;
233  }
234  [head removeFromSuperview];
235
236  if (indexInFlutterView > -1) {
237    // The chain was previously attached; attach it to the same position.
238    [flutter_view_.get() insertSubview:head atIndex:indexInFlutterView];
239  }
240  return head;
241}
242
243void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack,
244                                                   UIView* embedded_view) {
245  FML_DCHECK(CATransform3DEqualToTransform(embedded_view.layer.transform, CATransform3DIdentity));
246  UIView* head = embedded_view;
247  ResetAnchor(head.layer);
248
249  std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator iter = mutators_stack.Bottom();
250  while (iter != mutators_stack.Top()) {
251    switch ((*iter)->GetType()) {
252      case transform: {
253        CATransform3D transform = GetCATransform3DFromSkMatrix((*iter)->GetMatrix());
254        head.layer.transform = CATransform3DConcat(head.layer.transform, transform);
255        break;
256      }
257      case clip_rect:
258      case clip_rrect:
259      case clip_path: {
260        ChildClippingView* clipView = (ChildClippingView*)head.superview;
261        clipView.layer.transform = CATransform3DIdentity;
262        [clipView setClip:(*iter)->GetType()
263                     rect:(*iter)->GetRect()
264                    rrect:(*iter)->GetRRect()
265                     path:(*iter)->GetPath()];
266        ResetAnchor(clipView.layer);
267        head = clipView;
268        break;
269      }
270      case opacity:
271        embedded_view.alpha = (*iter)->GetAlphaFloat() * embedded_view.alpha;
272        break;
273    }
274    ++iter;
275  }
276  // Reverse scale based on screen scale.
277  //
278  // The UIKit frame is set based on the logical resolution instead of physical.
279  // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html).
280  // However, flow is based on the physical resolution. For eaxmple, 1000 pixels in flow equals
281  // 500 points in UIKit. And until this point, we did all the calculation based on the flow
282  // resolution. So we need to scale down to match UIKit's logical resolution.
283  CGFloat screenScale = [UIScreen mainScreen].scale;
284  head.layer.transform = CATransform3DConcat(
285      head.layer.transform, CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1));
286}
287
288void FlutterPlatformViewsController::CompositeWithParams(int view_id,
289                                                         const EmbeddedViewParams& params) {
290  CGRect frame = CGRectMake(0, 0, params.sizePoints.width(), params.sizePoints.height());
291  UIView* touchInterceptor = touch_interceptors_[view_id].get();
292  touchInterceptor.layer.transform = CATransform3DIdentity;
293  touchInterceptor.frame = frame;
294  touchInterceptor.alpha = 1;
295
296  int currentClippingCount = CountClips(params.mutatorsStack);
297  int previousClippingCount = clip_count_[view_id];
298  if (currentClippingCount != previousClippingCount) {
299    clip_count_[view_id] = currentClippingCount;
300    // If we have a different clipping count in this frame, we need to reconstruct the
301    // ClippingChildView chain to prepare for `ApplyMutators`.
302    UIView* oldPlatformViewRoot = root_views_[view_id].get();
303    UIView* newPlatformViewRoot =
304        ReconstructClipViewsChain(currentClippingCount, touchInterceptor, oldPlatformViewRoot);
305    root_views_[view_id] = fml::scoped_nsobject<UIView>([newPlatformViewRoot retain]);
306  }
307  ApplyMutators(params.mutatorsStack, touchInterceptor);
308}
309
310SkCanvas* FlutterPlatformViewsController::CompositeEmbeddedView(int view_id) {
311  // TODO(amirh): assert that this is running on the platform thread once we support the iOS
312  // embedded views thread configuration.
313
314  // Do nothing if the view doesn't need to be composited.
315  if (views_to_recomposite_.count(view_id) == 0) {
316    return picture_recorders_[view_id]->getRecordingCanvas();
317  }
318  CompositeWithParams(view_id, current_composition_params_[view_id]);
319  views_to_recomposite_.erase(view_id);
320  return picture_recorders_[view_id]->getRecordingCanvas();
321}
322
323void FlutterPlatformViewsController::Reset() {
324  UIView* flutter_view = flutter_view_.get();
325  for (UIView* sub_view in [flutter_view subviews]) {
326    [sub_view removeFromSuperview];
327  }
328  views_.clear();
329  overlays_.clear();
330  composition_order_.clear();
331  active_composition_order_.clear();
332  picture_recorders_.clear();
333  current_composition_params_.clear();
334  clip_count_.clear();
335  views_to_recomposite_.clear();
336}
337
338bool FlutterPlatformViewsController::SubmitFrame(bool gl_rendering,
339                                                 GrContext* gr_context,
340                                                 std::shared_ptr<IOSGLContext> gl_context) {
341  DisposeViews();
342
343  bool did_submit = true;
344  for (size_t i = 0; i < composition_order_.size(); i++) {
345    int64_t view_id = composition_order_[i];
346    if (gl_rendering) {
347      EnsureGLOverlayInitialized(view_id, gl_context, gr_context);
348    } else {
349      EnsureOverlayInitialized(view_id);
350    }
351    auto frame = overlays_[view_id]->surface->AcquireFrame(frame_size_);
352    SkCanvas* canvas = frame->SkiaCanvas();
353    canvas->drawPicture(picture_recorders_[view_id]->finishRecordingAsPicture());
354    canvas->flush();
355    did_submit &= frame->Submit();
356  }
357  picture_recorders_.clear();
358  if (composition_order_ == active_composition_order_) {
359    composition_order_.clear();
360    return did_submit;
361  }
362  DetachUnusedLayers();
363  active_composition_order_.clear();
364  UIView* flutter_view = flutter_view_.get();
365
366  for (size_t i = 0; i < composition_order_.size(); i++) {
367    int view_id = composition_order_[i];
368    // We added a chain of super views to the platform view to handle clipping.
369    // The `platform_view_root` is the view at the top of the chain which is a direct subview of the
370    // `FlutterView`.
371    UIView* platform_view_root = root_views_[view_id].get();
372    UIView* overlay = overlays_[view_id]->overlay_view;
373    FML_CHECK(platform_view_root.superview == overlay.superview);
374    if (platform_view_root.superview == flutter_view) {
375      [flutter_view bringSubviewToFront:platform_view_root];
376      [flutter_view bringSubviewToFront:overlay];
377    } else {
378      [flutter_view addSubview:platform_view_root];
379      [flutter_view addSubview:overlay];
380    }
381
382    active_composition_order_.push_back(view_id);
383  }
384  composition_order_.clear();
385  return did_submit;
386}
387
388void FlutterPlatformViewsController::DetachUnusedLayers() {
389  std::unordered_set<int64_t> composition_order_set;
390
391  for (int64_t view_id : composition_order_) {
392    composition_order_set.insert(view_id);
393  }
394
395  for (int64_t view_id : active_composition_order_) {
396    if (composition_order_set.find(view_id) == composition_order_set.end()) {
397      if (root_views_.find(view_id) == root_views_.end()) {
398        continue;
399      }
400      // We added a chain of super views to the platform view to handle clipping.
401      // The `platform_view_root` is the view at the top of the chain which is a direct subview of
402      // the `FlutterView`.
403      UIView* platform_view_root = root_views_[view_id].get();
404      [platform_view_root removeFromSuperview];
405      [overlays_[view_id]->overlay_view.get() removeFromSuperview];
406    }
407  }
408}
409
410void FlutterPlatformViewsController::DisposeViews() {
411  if (views_to_dispose_.empty()) {
412    return;
413  }
414
415  for (int64_t viewId : views_to_dispose_) {
416    UIView* root_view = root_views_[viewId].get();
417    [root_view removeFromSuperview];
418    views_.erase(viewId);
419    touch_interceptors_.erase(viewId);
420    root_views_.erase(viewId);
421    if (overlays_.find(viewId) != overlays_.end()) {
422      [overlays_[viewId]->overlay_view.get() removeFromSuperview];
423    }
424    overlays_.erase(viewId);
425    current_composition_params_.erase(viewId);
426    clip_count_.erase(viewId);
427    views_to_recomposite_.erase(viewId);
428  }
429  views_to_dispose_.clear();
430}
431
432void FlutterPlatformViewsController::EnsureOverlayInitialized(int64_t overlay_id) {
433  if (overlays_.count(overlay_id) != 0) {
434    return;
435  }
436  FlutterOverlayView* overlay_view = [[FlutterOverlayView alloc] init];
437  overlay_view.frame = flutter_view_.get().bounds;
438  overlay_view.autoresizingMask =
439      (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
440  std::unique_ptr<IOSSurface> ios_surface = overlay_view.createSoftwareSurface;
441  std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface();
442  overlays_[overlay_id] = std::make_unique<FlutterPlatformViewLayer>(
443      fml::scoped_nsobject<UIView>(overlay_view), std::move(ios_surface), std::move(surface));
444}
445
446void FlutterPlatformViewsController::EnsureGLOverlayInitialized(
447    int64_t overlay_id,
448    std::shared_ptr<IOSGLContext> gl_context,
449    GrContext* gr_context) {
450  if (overlays_.count(overlay_id) != 0) {
451    if (gr_context != overlays_gr_context_) {
452      overlays_gr_context_ = gr_context;
453      // The overlay already exists, but the GrContext was changed so we need to recreate
454      // the rendering surface with the new GrContext.
455      IOSSurfaceGL* ios_surface_gl = (IOSSurfaceGL*)overlays_[overlay_id]->ios_surface.get();
456      std::unique_ptr<Surface> surface = ios_surface_gl->CreateSecondaryGPUSurface(gr_context);
457      overlays_[overlay_id]->surface = std::move(surface);
458    }
459    return;
460  }
461  auto contentsScale = flutter_view_.get().layer.contentsScale;
462  FlutterOverlayView* overlay_view =
463      [[FlutterOverlayView alloc] initWithContentsScale:contentsScale];
464  overlay_view.frame = flutter_view_.get().bounds;
465  overlay_view.autoresizingMask =
466      (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
467  std::unique_ptr<IOSSurfaceGL> ios_surface =
468      [overlay_view createGLSurfaceWithContext:std::move(gl_context)];
469  std::unique_ptr<Surface> surface = ios_surface->CreateSecondaryGPUSurface(gr_context);
470  overlays_[overlay_id] = std::make_unique<FlutterPlatformViewLayer>(
471      fml::scoped_nsobject<UIView>(overlay_view), std::move(ios_surface), std::move(surface));
472  overlays_gr_context_ = gr_context;
473}
474
475}  // namespace flutter
476
477// This recognizers delays touch events from being dispatched to the responder chain until it failed
478// recognizing a gesture.
479//
480// We only fail this recognizer when asked to do so by the Flutter framework (which does so by
481// invoking an acceptGesture method on the platform_views channel). And this is how we allow the
482// Flutter framework to delay or prevent the embedded view from getting a touch sequence.
483@interface DelayingGestureRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>
484- (instancetype)initWithTarget:(id)target
485                        action:(SEL)action
486          forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer;
487@end
488
489// While the DelayingGestureRecognizer is preventing touches from hitting the responder chain
490// the touch events are not arriving to the FlutterView (and thus not arriving to the Flutter
491// framework). We use this gesture recognizer to dispatch the events directly to the FlutterView
492// while during this phase.
493//
494// If the Flutter framework decides to dispatch events to the embedded view, we fail the
495// DelayingGestureRecognizer which sends the events up the responder chain. But since the events
496// are handled by the embedded view they are not delivered to the Flutter framework in this phase
497// as well. So during this phase as well the ForwardingGestureRecognizer dispatched the events
498// directly to the FlutterView.
499@interface ForwardingGestureRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>
500- (instancetype)initWithTarget:(id)target
501         flutterViewController:(UIViewController*)flutterViewController;
502@end
503
504@implementation FlutterTouchInterceptingView {
505  fml::scoped_nsobject<DelayingGestureRecognizer> _delayingRecognizer;
506}
507- (instancetype)initWithEmbeddedView:(UIView*)embeddedView
508               flutterViewController:(UIViewController*)flutterViewController {
509  self = [super initWithFrame:embeddedView.frame];
510  if (self) {
511    self.multipleTouchEnabled = YES;
512    embeddedView.autoresizingMask =
513        (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
514
515    [self addSubview:embeddedView];
516
517    ForwardingGestureRecognizer* forwardingRecognizer =
518        [[[ForwardingGestureRecognizer alloc] initWithTarget:self
519                                       flutterViewController:flutterViewController] autorelease];
520
521    _delayingRecognizer.reset([[DelayingGestureRecognizer alloc]
522              initWithTarget:self
523                      action:nil
524        forwardingRecognizer:forwardingRecognizer]);
525
526    [self addGestureRecognizer:_delayingRecognizer.get()];
527    [self addGestureRecognizer:forwardingRecognizer];
528  }
529  return self;
530}
531
532- (void)releaseGesture {
533  _delayingRecognizer.get().state = UIGestureRecognizerStateFailed;
534}
535
536- (void)blockGesture {
537  _delayingRecognizer.get().state = UIGestureRecognizerStateEnded;
538}
539
540// We want the intercepting view to consume the touches and not pass the touches up to the parent
541// view. Make the touch event method not call super will not pass the touches up to the parent view.
542// Hence we overide the touch event methods and do nothing.
543- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
544}
545
546- (void)touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
547}
548
549- (void)touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
550}
551
552- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
553}
554
555@end
556
557@implementation DelayingGestureRecognizer {
558  fml::scoped_nsobject<UIGestureRecognizer> _forwardingRecognizer;
559}
560
561- (instancetype)initWithTarget:(id)target
562                        action:(SEL)action
563          forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer {
564  self = [super initWithTarget:target action:action];
565  if (self) {
566    self.delaysTouchesBegan = YES;
567    self.delegate = self;
568    _forwardingRecognizer.reset([forwardingRecognizer retain]);
569  }
570  return self;
571}
572
573- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
574    shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
575  // The forwarding gesture recognizer should always get all touch events, so it should not be
576  // required to fail by any other gesture recognizer.
577  return otherGestureRecognizer != _forwardingRecognizer.get() && otherGestureRecognizer != self;
578}
579
580- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
581    shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
582  return otherGestureRecognizer == self;
583}
584
585- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
586  self.state = UIGestureRecognizerStateFailed;
587}
588@end
589
590@implementation ForwardingGestureRecognizer {
591  // We can't dispatch events to the framework without this back pointer.
592  // This is a weak reference, the ForwardingGestureRecognizer is owned by the
593  // FlutterTouchInterceptingView which is strong referenced only by the FlutterView,
594  // which is strongly referenced by the FlutterViewController.
595  // So this is safe as when FlutterView is deallocated the reference to ForwardingGestureRecognizer
596  // will go away.
597  UIViewController* _flutterViewController;
598  // Counting the pointers that has started in one touch sequence.
599  NSInteger _currentTouchPointersCount;
600}
601
602- (instancetype)initWithTarget:(id)target
603         flutterViewController:(UIViewController*)flutterViewController {
604  self = [super initWithTarget:target action:nil];
605  if (self) {
606    self.delegate = self;
607    _flutterViewController = flutterViewController;
608    _currentTouchPointersCount = 0;
609  }
610  return self;
611}
612
613- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
614  [_flutterViewController touchesBegan:touches withEvent:event];
615  _currentTouchPointersCount += touches.count;
616}
617
618- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
619  [_flutterViewController touchesMoved:touches withEvent:event];
620}
621
622- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
623  [_flutterViewController touchesEnded:touches withEvent:event];
624  _currentTouchPointersCount -= touches.count;
625  // Touches in one touch sequence are sent to the touchesEnded method separately if different
626  // fingers stop touching the screen at different time. So one touchesEnded method triggering does
627  // not necessarially mean the touch sequence has ended. We Only set the state to
628  // UIGestureRecognizerStateFailed when all the touches in the current touch sequence is ended.
629  if (_currentTouchPointersCount == 0) {
630    self.state = UIGestureRecognizerStateFailed;
631  }
632}
633
634- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
635  [_flutterViewController touchesCancelled:touches withEvent:event];
636  _currentTouchPointersCount = 0;
637  self.state = UIGestureRecognizerStateFailed;
638}
639
640- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
641    shouldRecognizeSimultaneouslyWithGestureRecognizer:
642        (UIGestureRecognizer*)otherGestureRecognizer {
643  return YES;
644}
645@end
646