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