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