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