Flutter iOS Embedder
FlutterPlatformViews.mm
Go to the documentation of this file.
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 #include <Metal/Metal.h>
6 #import <UIKit/UIGestureRecognizerSubclass.h>
7 
8 #include <list>
9 #include <map>
10 #include <memory>
11 #include <string>
12 
13 #include "flutter/common/graphics/persistent_cache.h"
14 #include "flutter/fml/platform/darwin/scoped_nsobject.h"
21 
22 @implementation UIView (FirstResponder)
24  if (self.isFirstResponder) {
25  return YES;
26  }
27  for (UIView* subview in self.subviews) {
28  if (subview.flt_hasFirstResponderInViewHierarchySubtree) {
29  return YES;
30  }
31  }
32  return NO;
33 }
34 @end
35 
36 // Determines if the `clip_rect` from a clipRect mutator contains the
37 // `platformview_boundingrect`.
38 //
39 // `clip_rect` is in its own coordinate space. The rect needs to be transformed by
40 // `transform_matrix` to be in the coordinate space where the PlatformView is displayed.
41 //
42 // `platformview_boundingrect` is the final bounding rect of the PlatformView in the coordinate
43 // space where the PlatformView is displayed.
44 static bool ClipRectContainsPlatformViewBoundingRect(const SkRect& clip_rect,
45  const SkRect& platformview_boundingrect,
46  const SkMatrix& transform_matrix) {
47  SkRect transformed_rect = transform_matrix.mapRect(clip_rect);
48  return transformed_rect.contains(platformview_boundingrect);
49 }
50 
51 // Determines if the `clipRRect` from a clipRRect mutator contains the
52 // `platformview_boundingrect`.
53 //
54 // `clip_rrect` is in its own coordinate space. The rrect needs to be transformed by
55 // `transform_matrix` to be in the coordinate space where the PlatformView is displayed.
56 //
57 // `platformview_boundingrect` is the final bounding rect of the PlatformView in the coordinate
58 // space where the PlatformView is displayed.
59 static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
60  const SkRect& platformview_boundingrect,
61  const SkMatrix& transform_matrix) {
62  SkVector upper_left = clip_rrect.radii(SkRRect::Corner::kUpperLeft_Corner);
63  SkVector upper_right = clip_rrect.radii(SkRRect::Corner::kUpperRight_Corner);
64  SkVector lower_right = clip_rrect.radii(SkRRect::Corner::kLowerRight_Corner);
65  SkVector lower_left = clip_rrect.radii(SkRRect::Corner::kLowerLeft_Corner);
66  SkScalar transformed_upper_left_x = transform_matrix.mapRadius(upper_left.x());
67  SkScalar transformed_upper_left_y = transform_matrix.mapRadius(upper_left.y());
68  SkScalar transformed_upper_right_x = transform_matrix.mapRadius(upper_right.x());
69  SkScalar transformed_upper_right_y = transform_matrix.mapRadius(upper_right.y());
70  SkScalar transformed_lower_right_x = transform_matrix.mapRadius(lower_right.x());
71  SkScalar transformed_lower_right_y = transform_matrix.mapRadius(lower_right.y());
72  SkScalar transformed_lower_left_x = transform_matrix.mapRadius(lower_left.x());
73  SkScalar transformed_lower_left_y = transform_matrix.mapRadius(lower_left.y());
74  SkRect transformed_clip_rect = transform_matrix.mapRect(clip_rrect.rect());
75  SkRRect transformed_rrect;
76  SkVector corners[] = {{transformed_upper_left_x, transformed_upper_left_y},
77  {transformed_upper_right_x, transformed_upper_right_y},
78  {transformed_lower_right_x, transformed_lower_right_y},
79  {transformed_lower_left_x, transformed_lower_left_y}};
80  transformed_rrect.setRectRadii(transformed_clip_rect, corners);
81  return transformed_rrect.contains(platformview_boundingrect);
82 }
83 
84 namespace flutter {
85 // Becomes NO if Apple's API changes and blurred backdrop filters cannot be applied.
87 
88 std::shared_ptr<FlutterPlatformViewLayer> FlutterPlatformViewLayerPool::GetLayer(
89  GrDirectContext* gr_context,
90  const std::shared_ptr<IOSContext>& ios_context,
91  MTLPixelFormat pixel_format) {
92  if (available_layer_index_ >= layers_.size()) {
93  std::shared_ptr<FlutterPlatformViewLayer> layer;
94  fml::scoped_nsobject<UIView> overlay_view;
95  fml::scoped_nsobject<UIView> overlay_view_wrapper;
96 
97  bool impeller_enabled = !!ios_context->GetImpellerContext();
98  if (!gr_context && !impeller_enabled) {
99  overlay_view.reset([[FlutterOverlayView alloc] init]);
100  overlay_view_wrapper.reset([[FlutterOverlayView alloc] init]);
101 
102  auto ca_layer = fml::scoped_nsobject<CALayer>{[[overlay_view.get() layer] retain]};
103  std::unique_ptr<IOSSurface> ios_surface = IOSSurface::Create(ios_context, ca_layer);
104  std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface();
105 
106  layer = std::make_shared<FlutterPlatformViewLayer>(
107  std::move(overlay_view), std::move(overlay_view_wrapper), std::move(ios_surface),
108  std::move(surface));
109  } else {
110  CGFloat screenScale = [UIScreen mainScreen].scale;
111  overlay_view.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale
112  pixelFormat:pixel_format]);
113  overlay_view_wrapper.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale
114  pixelFormat:pixel_format]);
115 
116  auto ca_layer = fml::scoped_nsobject<CALayer>{[[overlay_view.get() layer] retain]};
117  std::unique_ptr<IOSSurface> ios_surface = IOSSurface::Create(ios_context, ca_layer);
118  std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface(gr_context);
119 
120  layer = std::make_shared<FlutterPlatformViewLayer>(
121  std::move(overlay_view), std::move(overlay_view_wrapper), std::move(ios_surface),
122  std::move(surface));
123  layer->gr_context = gr_context;
124  }
125  // The overlay view wrapper masks the overlay view.
126  // This is required to keep the backing surface size unchanged between frames.
127  //
128  // Otherwise, changing the size of the overlay would require a new surface,
129  // which can be very expensive.
130  //
131  // This is the case of an animation in which the overlay size is changing in every frame.
132  //
133  // +------------------------+
134  // | overlay_view |
135  // | +--------------+ | +--------------+
136  // | | wrapper | | == mask => | overlay_view |
137  // | +--------------+ | +--------------+
138  // +------------------------+
139  layer->overlay_view_wrapper.get().clipsToBounds = YES;
140  [layer->overlay_view_wrapper.get() addSubview:layer->overlay_view];
141  layers_.push_back(layer);
142  }
143  std::shared_ptr<FlutterPlatformViewLayer> layer = layers_[available_layer_index_];
144  if (gr_context != layer->gr_context) {
145  layer->gr_context = gr_context;
146  // The overlay already exists, but the GrContext was changed so we need to recreate
147  // the rendering surface with the new GrContext.
148  IOSSurface* ios_surface = layer->ios_surface.get();
149  std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface(gr_context);
150  layer->surface = std::move(surface);
151  }
152  available_layer_index_++;
153  return layer;
154 }
155 
156 void FlutterPlatformViewLayerPool::RecycleLayers() {
157  available_layer_index_ = 0;
158 }
159 
160 std::vector<std::shared_ptr<FlutterPlatformViewLayer>>
161 FlutterPlatformViewLayerPool::GetUnusedLayers() {
162  std::vector<std::shared_ptr<FlutterPlatformViewLayer>> results;
163  for (size_t i = available_layer_index_; i < layers_.size(); i++) {
164  results.push_back(layers_[i]);
165  }
166  return results;
167 }
168 
169 void FlutterPlatformViewsController::SetFlutterView(UIView* flutter_view) {
170  flutter_view_.reset([flutter_view retain]);
171 }
172 
173 void FlutterPlatformViewsController::SetFlutterViewController(
174  UIViewController* flutter_view_controller) {
175  flutter_view_controller_.reset([flutter_view_controller retain]);
176 }
177 
178 UIViewController* FlutterPlatformViewsController::getFlutterViewController() {
179  return flutter_view_controller_.get();
180 }
181 
182 void FlutterPlatformViewsController::OnMethodCall(FlutterMethodCall* call, FlutterResult result) {
183  if ([[call method] isEqualToString:@"create"]) {
184  OnCreate(call, result);
185  } else if ([[call method] isEqualToString:@"dispose"]) {
186  OnDispose(call, result);
187  } else if ([[call method] isEqualToString:@"acceptGesture"]) {
188  OnAcceptGesture(call, result);
189  } else if ([[call method] isEqualToString:@"rejectGesture"]) {
190  OnRejectGesture(call, result);
191  } else {
193  }
194 }
195 
196 void FlutterPlatformViewsController::OnCreate(FlutterMethodCall* call, FlutterResult result) {
197  NSDictionary<NSString*, id>* args = [call arguments];
198 
199  int64_t viewId = [args[@"id"] longLongValue];
200  NSString* viewTypeString = args[@"viewType"];
201  std::string viewType(viewTypeString.UTF8String);
202 
203  if (views_.count(viewId) != 0) {
204  result([FlutterError errorWithCode:@"recreating_view"
205  message:@"trying to create an already created view"
206  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
207  }
208 
209  NSObject<FlutterPlatformViewFactory>* factory = factories_[viewType].get();
210  if (factory == nil) {
211  result([FlutterError
212  errorWithCode:@"unregistered_view_type"
213  message:[NSString stringWithFormat:@"A UIKitView widget is trying to create a "
214  @"PlatformView with an unregistered type: < %@ >",
215  viewTypeString]
216  details:@"If you are the author of the PlatformView, make sure `registerViewFactory` "
217  @"is invoked.\n"
218  @"See: "
219  @"https://docs.flutter.dev/development/platform-integration/"
220  @"platform-views#on-the-platform-side-1 for more details.\n"
221  @"If you are not the author of the PlatformView, make sure to call "
222  @"`GeneratedPluginRegistrant.register`."]);
223  return;
224  }
225 
226  id params = nil;
227  if ([factory respondsToSelector:@selector(createArgsCodec)]) {
228  NSObject<FlutterMessageCodec>* codec = [factory createArgsCodec];
229  if (codec != nil && args[@"params"] != nil) {
230  FlutterStandardTypedData* paramsData = args[@"params"];
231  params = [codec decode:paramsData.data];
232  }
233  }
234 
235  NSObject<FlutterPlatformView>* embedded_view = [factory createWithFrame:CGRectZero
236  viewIdentifier:viewId
237  arguments:params];
238  UIView* platform_view = [embedded_view view];
239  // Set a unique view identifier, so the platform view can be identified in unit tests.
240  platform_view.accessibilityIdentifier =
241  [NSString stringWithFormat:@"platform_view[%lld]", viewId];
242  views_[viewId] = fml::scoped_nsobject<NSObject<FlutterPlatformView>>([embedded_view retain]);
243 
244  FlutterTouchInterceptingView* touch_interceptor = [[[FlutterTouchInterceptingView alloc]
245  initWithEmbeddedView:platform_view
246  platformViewsController:GetWeakPtr()
247  gestureRecognizersBlockingPolicy:gesture_recognizers_blocking_policies_[viewType]]
248  autorelease];
249 
250  touch_interceptors_[viewId] =
251  fml::scoped_nsobject<FlutterTouchInterceptingView>([touch_interceptor retain]);
252 
253  ChildClippingView* clipping_view =
254  [[[ChildClippingView alloc] initWithFrame:CGRectZero] autorelease];
255  [clipping_view addSubview:touch_interceptor];
256  root_views_[viewId] = fml::scoped_nsobject<UIView>([clipping_view retain]);
257 
258  result(nil);
259 }
260 
261 void FlutterPlatformViewsController::OnDispose(FlutterMethodCall* call, FlutterResult result) {
262  NSNumber* arg = [call arguments];
263  int64_t viewId = [arg longLongValue];
264 
265  if (views_.count(viewId) == 0) {
266  result([FlutterError errorWithCode:@"unknown_view"
267  message:@"trying to dispose an unknown"
268  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
269  return;
270  }
271  // We wait for next submitFrame to dispose views.
272  views_to_dispose_.insert(viewId);
273  result(nil);
274 }
275 
276 void FlutterPlatformViewsController::OnAcceptGesture(FlutterMethodCall* call,
277  FlutterResult result) {
278  NSDictionary<NSString*, id>* args = [call arguments];
279  int64_t viewId = [args[@"id"] longLongValue];
280 
281  if (views_.count(viewId) == 0) {
282  result([FlutterError errorWithCode:@"unknown_view"
283  message:@"trying to set gesture state for an unknown view"
284  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
285  return;
286  }
287 
288  FlutterTouchInterceptingView* view = touch_interceptors_[viewId].get();
289  [view releaseGesture];
290 
291  result(nil);
292 }
293 
294 void FlutterPlatformViewsController::OnRejectGesture(FlutterMethodCall* call,
295  FlutterResult result) {
296  NSDictionary<NSString*, id>* args = [call arguments];
297  int64_t viewId = [args[@"id"] longLongValue];
298 
299  if (views_.count(viewId) == 0) {
300  result([FlutterError errorWithCode:@"unknown_view"
301  message:@"trying to set gesture state for an unknown view"
302  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
303  return;
304  }
305 
306  FlutterTouchInterceptingView* view = touch_interceptors_[viewId].get();
307  [view blockGesture];
308 
309  result(nil);
310 }
311 
312 void FlutterPlatformViewsController::RegisterViewFactory(
313  NSObject<FlutterPlatformViewFactory>* factory,
314  NSString* factoryId,
315  FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy) {
316  std::string idString([factoryId UTF8String]);
317  FML_CHECK(factories_.count(idString) == 0);
318  factories_[idString] =
319  fml::scoped_nsobject<NSObject<FlutterPlatformViewFactory>>([factory retain]);
320  gesture_recognizers_blocking_policies_[idString] = gestureRecognizerBlockingPolicy;
321 }
322 
323 void FlutterPlatformViewsController::BeginFrame(SkISize frame_size) {
324  ResetFrameState();
325  frame_size_ = frame_size;
326 }
327 
328 void FlutterPlatformViewsController::CancelFrame() {
329  ResetFrameState();
330 }
331 
332 // TODO(cyanglaz): https://github.com/flutter/flutter/issues/56474
333 // Make this method check if there are pending view operations instead.
334 // Also rename it to `HasPendingViewOperations`.
335 bool FlutterPlatformViewsController::HasPlatformViewThisOrNextFrame() {
336  return !composition_order_.empty() || !active_composition_order_.empty();
337 }
338 
339 const int FlutterPlatformViewsController::kDefaultMergedLeaseDuration;
340 
341 PostPrerollResult FlutterPlatformViewsController::PostPrerollAction(
342  const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
343  // TODO(cyanglaz): https://github.com/flutter/flutter/issues/56474
344  // Rename `has_platform_view` to `view_mutated` when the above issue is resolved.
345  if (!HasPlatformViewThisOrNextFrame()) {
346  return PostPrerollResult::kSuccess;
347  }
348  if (!raster_thread_merger->IsMerged()) {
349  // The raster thread merger may be disabled if the rasterizer is being
350  // created or teared down.
351  //
352  // In such cases, the current frame is dropped, and a new frame is attempted
353  // with the same layer tree.
354  //
355  // Eventually, the frame is submitted once this method returns `kSuccess`.
356  // At that point, the raster tasks are handled on the platform thread.
357  CancelFrame();
358  return PostPrerollResult::kSkipAndRetryFrame;
359  }
360  // If the post preroll action is successful, we will display platform views in the current frame.
361  // In order to sync the rendering of the platform views (quartz) with skia's rendering,
362  // We need to begin an explicit CATransaction. This transaction needs to be submitted
363  // after the current frame is submitted.
364  BeginCATransaction();
365  raster_thread_merger->ExtendLeaseTo(kDefaultMergedLeaseDuration);
366  return PostPrerollResult::kSuccess;
367 }
368 
369 void FlutterPlatformViewsController::EndFrame(
370  bool should_resubmit_frame,
371  const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {
372  if (should_resubmit_frame) {
373  raster_thread_merger->MergeWithLease(kDefaultMergedLeaseDuration);
374  }
375 }
376 
377 void FlutterPlatformViewsController::PushFilterToVisitedPlatformViews(
378  const std::shared_ptr<const DlImageFilter>& filter,
379  const SkRect& filter_rect) {
380  for (int64_t id : visited_platform_views_) {
381  EmbeddedViewParams params = current_composition_params_[id];
382  params.PushImageFilter(filter, filter_rect);
383  current_composition_params_[id] = params;
384  }
385 }
386 
387 void FlutterPlatformViewsController::PrerollCompositeEmbeddedView(
388  int64_t view_id,
389  std::unique_ptr<EmbeddedViewParams> params) {
390  // All the CATransactions should be committed by the end of the last frame,
391  // so catransaction_added_ must be false.
392  FML_DCHECK(!catransaction_added_);
393 
394  SkRect view_bounds = SkRect::Make(frame_size_);
395  std::unique_ptr<EmbedderViewSlice> view;
396  view = std::make_unique<DisplayListEmbedderViewSlice>(view_bounds);
397  slices_.insert_or_assign(view_id, std::move(view));
398 
399  composition_order_.push_back(view_id);
400 
401  if (current_composition_params_.count(view_id) == 1 &&
402  current_composition_params_[view_id] == *params.get()) {
403  // Do nothing if the params didn't change.
404  return;
405  }
406  current_composition_params_[view_id] = EmbeddedViewParams(*params.get());
407  views_to_recomposite_.insert(view_id);
408 }
409 
410 size_t FlutterPlatformViewsController::EmbeddedViewCount() {
411  return composition_order_.size();
412 }
413 
414 UIView* FlutterPlatformViewsController::GetPlatformViewByID(int64_t view_id) {
416 }
417 
418 FlutterTouchInterceptingView* FlutterPlatformViewsController::GetFlutterTouchInterceptingViewByID(
419  int64_t view_id) {
420  if (views_.empty()) {
421  return nil;
422  }
423  return touch_interceptors_[view_id].get();
424 }
425 
426 long FlutterPlatformViewsController::FindFirstResponderPlatformViewId() {
427  for (auto const& [id, root_view] : root_views_) {
428  if ((UIView*)(root_view.get()).flt_hasFirstResponderInViewHierarchySubtree) {
429  return id;
430  }
431  }
432  return -1;
433 }
434 
435 int FlutterPlatformViewsController::CountClips(const MutatorsStack& mutators_stack) {
436  std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator iter = mutators_stack.Bottom();
437  int clipCount = 0;
438  while (iter != mutators_stack.Top()) {
439  if ((*iter)->IsClipType()) {
440  clipCount++;
441  }
442  ++iter;
443  }
444  return clipCount;
445 }
446 
447 void FlutterPlatformViewsController::ClipViewSetMaskView(UIView* clipView) {
448  if (clipView.maskView) {
449  return;
450  }
451  UIView* flutterView = flutter_view_.get();
452  CGRect frame =
453  CGRectMake(-clipView.frame.origin.x, -clipView.frame.origin.y,
454  CGRectGetWidth(flutterView.bounds), CGRectGetHeight(flutterView.bounds));
455  clipView.maskView = [mask_view_pool_.get() getMaskViewWithFrame:frame];
456 }
457 
458 // This method is only called when the `embedded_view` needs to be re-composited at the current
459 // frame. See: `CompositeWithParams` for details.
460 void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack,
461  UIView* embedded_view,
462  const SkRect& bounding_rect) {
463  if (flutter_view_ == nullptr) {
464  return;
465  }
466  FML_DCHECK(CATransform3DEqualToTransform(embedded_view.layer.transform, CATransform3DIdentity));
467  ResetAnchor(embedded_view.layer);
468  ChildClippingView* clipView = (ChildClippingView*)embedded_view.superview;
469 
470  SkMatrix transformMatrix;
471  NSMutableArray* blurFilters = [[[NSMutableArray alloc] init] autorelease];
472  FML_DCHECK(!clipView.maskView ||
473  [clipView.maskView isKindOfClass:[FlutterClippingMaskView class]]);
474  if (clipView.maskView) {
475  [mask_view_pool_.get() insertViewToPoolIfNeeded:(FlutterClippingMaskView*)(clipView.maskView)];
476  clipView.maskView = nil;
477  }
478  CGFloat screenScale = [UIScreen mainScreen].scale;
479  auto iter = mutators_stack.Begin();
480  while (iter != mutators_stack.End()) {
481  switch ((*iter)->GetType()) {
482  case kTransform: {
483  transformMatrix.preConcat((*iter)->GetMatrix());
484  break;
485  }
486  case kClipRect: {
487  if (ClipRectContainsPlatformViewBoundingRect((*iter)->GetRect(), bounding_rect,
488  transformMatrix)) {
489  break;
490  }
491  ClipViewSetMaskView(clipView);
492  [(FlutterClippingMaskView*)clipView.maskView clipRect:(*iter)->GetRect()
493  matrix:transformMatrix];
494  break;
495  }
496  case kClipRRect: {
497  if (ClipRRectContainsPlatformViewBoundingRect((*iter)->GetRRect(), bounding_rect,
498  transformMatrix)) {
499  break;
500  }
501  ClipViewSetMaskView(clipView);
502  [(FlutterClippingMaskView*)clipView.maskView clipRRect:(*iter)->GetRRect()
503  matrix:transformMatrix];
504  break;
505  }
506  case kClipPath: {
507  // TODO(cyanglaz): Find a way to pre-determine if path contains the PlatformView boudning
508  // rect. See `ClipRRectContainsPlatformViewBoundingRect`.
509  // https://github.com/flutter/flutter/issues/118650
510  ClipViewSetMaskView(clipView);
511  [(FlutterClippingMaskView*)clipView.maskView clipPath:(*iter)->GetPath()
512  matrix:transformMatrix];
513  break;
514  }
515  case kOpacity:
516  embedded_view.alpha = (*iter)->GetAlphaFloat() * embedded_view.alpha;
517  break;
518  case kBackdropFilter: {
519  // Only support DlBlurImageFilter for BackdropFilter.
520  if (!canApplyBlurBackdrop || !(*iter)->GetFilterMutation().GetFilter().asBlur()) {
521  break;
522  }
523  CGRect filterRect =
524  flutter::GetCGRectFromSkRect((*iter)->GetFilterMutation().GetFilterRect());
525  // `filterRect` is in global coordinates. We need to convert to local space.
526  filterRect = CGRectApplyAffineTransform(
527  filterRect, CGAffineTransformMakeScale(1 / screenScale, 1 / screenScale));
528  // `filterRect` reprents the rect that should be filtered inside the `flutter_view_`.
529  // The `PlatformViewFilter` needs the frame inside the `clipView` that needs to be
530  // filtered.
531  if (CGRectIsNull(CGRectIntersection(filterRect, clipView.frame))) {
532  break;
533  }
534  CGRect intersection = CGRectIntersection(filterRect, clipView.frame);
535  CGRect frameInClipView = [flutter_view_.get() convertRect:intersection toView:clipView];
536  // sigma_x is arbitrarily chosen as the radius value because Quartz sets
537  // sigma_x and sigma_y equal to each other. DlBlurImageFilter's Tile Mode
538  // is not supported in Quartz's gaussianBlur CAFilter, so it is not used
539  // to blur the PlatformView.
540  CGFloat blurRadius = (*iter)->GetFilterMutation().GetFilter().asBlur()->sigma_x();
541  UIVisualEffectView* visualEffectView = [[[UIVisualEffectView alloc]
542  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]] autorelease];
543  PlatformViewFilter* filter =
544  [[[PlatformViewFilter alloc] initWithFrame:frameInClipView
545  blurRadius:blurRadius
546  visualEffectView:visualEffectView] autorelease];
547  if (!filter) {
549  } else {
550  [blurFilters addObject:filter];
551  }
552  break;
553  }
554  }
555  ++iter;
556  }
557 
558  if (canApplyBlurBackdrop) {
559  [clipView applyBlurBackdropFilters:blurFilters];
560  }
561 
562  // The UIKit frame is set based on the logical resolution (points) instead of physical.
563  // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html).
564  // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals
565  // 500 points in UIKit for devices that has screenScale of 2. We need to scale the transformMatrix
566  // down to the logical resoltion before applying it to the layer of PlatformView.
567  transformMatrix.postScale(1 / screenScale, 1 / screenScale);
568 
569  // Reverse the offset of the clipView.
570  // The clipView's frame includes the final translate of the final transform matrix.
571  // Thus, this translate needs to be reversed so the platform view can layout at the correct
572  // offset.
573  //
574  // Note that the transforms are not applied to the clipping paths because clipping paths happen on
575  // the mask view, whose origin is always (0,0) to the flutter_view.
576  transformMatrix.postTranslate(-clipView.frame.origin.x, -clipView.frame.origin.y);
577 
578  embedded_view.layer.transform = flutter::GetCATransform3DFromSkMatrix(transformMatrix);
579 }
580 
581 // Composite the PlatformView with `view_id`.
582 //
583 // Every frame, during the paint traversal of the layer tree, this method is called for all
584 // the PlatformViews in `views_to_recomposite_`.
585 //
586 // Note that `views_to_recomposite_` does not represent all the views in the view hierarchy,
587 // if a PlatformView does not change its composition parameter from last frame, it is not
588 // included in the `views_to_recomposite_`.
589 void FlutterPlatformViewsController::CompositeWithParams(int64_t view_id,
590  const EmbeddedViewParams& params) {
591  CGRect frame = CGRectMake(0, 0, params.sizePoints().width(), params.sizePoints().height());
592  FlutterTouchInterceptingView* touchInterceptor = touch_interceptors_[view_id].get();
593 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
594  FML_DCHECK(CGPointEqualToPoint([touchInterceptor embeddedView].frame.origin, CGPointZero));
595  if (non_zero_origin_views_.find(view_id) == non_zero_origin_views_.end() &&
596  !CGPointEqualToPoint([touchInterceptor embeddedView].frame.origin, CGPointZero)) {
597  non_zero_origin_views_.insert(view_id);
598  NSLog(
599  @"A Embedded PlatformView's origin is not CGPointZero.\n"
600  " View id: %@\n"
601  " View info: \n %@ \n"
602  "A non-zero origin might cause undefined behavior.\n"
603  "See https://github.com/flutter/flutter/issues/109700 for more details.\n"
604  "If you are the author of the PlatformView, please update the implementation of the "
605  "PlatformView to have a (0, 0) origin.\n"
606  "If you have a valid case of using a non-zero origin, "
607  "please leave a comment at https://github.com/flutter/flutter/issues/109700 with details.",
608  @(view_id), [touchInterceptor embeddedView]);
609  }
610 #endif
611  touchInterceptor.layer.transform = CATransform3DIdentity;
612  touchInterceptor.frame = frame;
613  touchInterceptor.alpha = 1;
614 
615  const MutatorsStack& mutatorStack = params.mutatorsStack();
616  UIView* clippingView = root_views_[view_id].get();
617  // The frame of the clipping view should be the final bounding rect.
618  // Because the translate matrix in the Mutator Stack also includes the offset,
619  // when we apply the transforms matrix in |ApplyMutators|, we need
620  // to remember to do a reverse translate.
621  const SkRect& rect = params.finalBoundingRect();
622  CGFloat screenScale = [UIScreen mainScreen].scale;
623  clippingView.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale,
624  rect.width() / screenScale, rect.height() / screenScale);
625  ApplyMutators(mutatorStack, touchInterceptor, rect);
626 }
627 
628 DlCanvas* FlutterPlatformViewsController::CompositeEmbeddedView(int64_t view_id) {
629  // Any UIKit related code has to run on main thread.
630  FML_DCHECK([[NSThread currentThread] isMainThread]);
631  // Do nothing if the view doesn't need to be composited.
632  if (views_to_recomposite_.count(view_id) == 0) {
633  return slices_[view_id]->canvas();
634  }
635  CompositeWithParams(view_id, current_composition_params_[view_id]);
636  views_to_recomposite_.erase(view_id);
637  return slices_[view_id]->canvas();
638 }
639 
640 void FlutterPlatformViewsController::Reset() {
641  for (int64_t view_id : active_composition_order_) {
642  UIView* sub_view = root_views_[view_id].get();
643  [sub_view removeFromSuperview];
644  }
645  root_views_.clear();
646  touch_interceptors_.clear();
647  views_.clear();
648  composition_order_.clear();
649  active_composition_order_.clear();
650  slices_.clear();
651  current_composition_params_.clear();
652  clip_count_.clear();
653  views_to_recomposite_.clear();
654  layer_pool_->RecycleLayers();
655  visited_platform_views_.clear();
656 }
657 
658 SkRect FlutterPlatformViewsController::GetPlatformViewRect(int64_t view_id) {
659  UIView* platform_view = GetPlatformViewByID(view_id);
660  UIScreen* screen = [UIScreen mainScreen];
661  CGRect platform_view_cgrect = [platform_view convertRect:platform_view.bounds
662  toView:flutter_view_];
663  return SkRect::MakeXYWH(platform_view_cgrect.origin.x * screen.scale, //
664  platform_view_cgrect.origin.y * screen.scale, //
665  platform_view_cgrect.size.width * screen.scale, //
666  platform_view_cgrect.size.height * screen.scale //
667  );
668 }
669 
670 bool FlutterPlatformViewsController::SubmitFrame(GrDirectContext* gr_context,
671  const std::shared_ptr<IOSContext>& ios_context,
672  std::unique_ptr<SurfaceFrame> frame) {
673  TRACE_EVENT0("flutter", "FlutterPlatformViewsController::SubmitFrame");
674 
675  // Any UIKit related code has to run on main thread.
676  FML_DCHECK([[NSThread currentThread] isMainThread]);
677  if (flutter_view_ == nullptr) {
678  return frame->Submit();
679  }
680 
681  DisposeViews();
682 
683  DlCanvas* background_canvas = frame->Canvas();
684 
685  // Resolve all pending GPU operations before allocating a new surface.
686  background_canvas->Flush();
687 
688  // Clipping the background canvas before drawing the picture recorders requires
689  // saving and restoring the clip context.
690  DlAutoCanvasRestore save(background_canvas, /*do_save=*/true);
691 
692  // Maps a platform view id to a vector of `FlutterPlatformViewLayer`.
693  LayersMap platform_view_layers;
694 
695  auto did_submit = true;
696  auto num_platform_views = composition_order_.size();
697 
698  for (size_t i = 0; i < num_platform_views; i++) {
699  int64_t platform_view_id = composition_order_[i];
700  EmbedderViewSlice* slice = slices_[platform_view_id].get();
701  slice->end_recording();
702 
703  // Check if the current picture contains overlays that intersect with the
704  // current platform view or any of the previous platform views.
705  for (size_t j = i + 1; j > 0; j--) {
706  int64_t current_platform_view_id = composition_order_[j - 1];
707  SkRect platform_view_rect = GetPlatformViewRect(current_platform_view_id);
708  std::vector<SkIRect> intersection_rects = slice->region(platform_view_rect).getRects();
709  auto allocation_size = intersection_rects.size();
710 
711  // For testing purposes, the overlay id is used to find the overlay view.
712  // This is the index of the layer for the current platform view.
713  auto overlay_id = platform_view_layers[current_platform_view_id].size();
714 
715  // If the max number of allocations per platform view is exceeded,
716  // then join all the rects into a single one.
717  //
718  // TODO(egarciad): Consider making this configurable.
719  // https://github.com/flutter/flutter/issues/52510
720  if (allocation_size > kMaxLayerAllocations) {
721  SkIRect joined_rect = SkIRect::MakeEmpty();
722  for (const SkIRect& rect : intersection_rects) {
723  joined_rect.join(rect);
724  }
725  // Replace the rects in the intersection rects list for a single rect that is
726  // the union of all the rects in the list.
727  intersection_rects.clear();
728  intersection_rects.push_back(joined_rect);
729  }
730  for (SkIRect& joined_rect : intersection_rects) {
731  // Get the intersection rect between the current rect
732  // and the platform view rect.
733  joined_rect.intersect(platform_view_rect.roundOut());
734  // Clip the background canvas, so it doesn't contain any of the pixels drawn
735  // on the overlay layer.
736  background_canvas->ClipRect(SkRect::Make(joined_rect), DlCanvas::ClipOp::kDifference);
737  // Get a new host layer.
738  std::shared_ptr<FlutterPlatformViewLayer> layer =
739  GetLayer(gr_context, //
740  ios_context, //
741  slice, //
742  joined_rect, //
743  current_platform_view_id, //
744  overlay_id, //
745  ((FlutterView*)flutter_view_.get()).pixelFormat //
746  );
747  did_submit &= layer->did_submit_last_frame;
748  platform_view_layers[current_platform_view_id].push_back(layer);
749  overlay_id++;
750  }
751  }
752  slice->render_into(background_canvas);
753  }
754 
755  // Manually trigger the SkAutoCanvasRestore before we submit the frame
756  save.Restore();
757 
758  // If a layer was allocated in the previous frame, but it's not used in the current frame,
759  // then it can be removed from the scene.
760  RemoveUnusedLayers();
761  // Organize the layers by their z indexes.
762  BringLayersIntoView(platform_view_layers);
763  // Mark all layers as available, so they can be used in the next frame.
764  layer_pool_->RecycleLayers();
765 
766  did_submit &= frame->Submit();
767 
768  // If the frame is submitted with embedded platform views,
769  // there should be a |[CATransaction begin]| call in this frame prior to all the drawing.
770  // If that case, we need to commit the transaction.
771  CommitCATransactionIfNeeded();
772  return did_submit;
773 }
774 
775 void FlutterPlatformViewsController::BringLayersIntoView(LayersMap layer_map) {
776  FML_DCHECK(flutter_view_);
777  UIView* flutter_view = flutter_view_.get();
778  // Clear the `active_composition_order_`, which will be populated down below.
779  active_composition_order_.clear();
780  NSMutableArray* desired_platform_subviews = [NSMutableArray array];
781  for (size_t i = 0; i < composition_order_.size(); i++) {
782  int64_t platform_view_id = composition_order_[i];
783  std::vector<std::shared_ptr<FlutterPlatformViewLayer>> layers = layer_map[platform_view_id];
784  UIView* platform_view_root = root_views_[platform_view_id].get();
785  [desired_platform_subviews addObject:platform_view_root];
786  for (const std::shared_ptr<FlutterPlatformViewLayer>& layer : layers) {
787  [desired_platform_subviews addObject:layer->overlay_view_wrapper];
788  }
789  active_composition_order_.push_back(platform_view_id);
790  }
791 
792  NSSet* desired_platform_subviews_set = [NSSet setWithArray:desired_platform_subviews];
793  NSArray* existing_platform_subviews = [flutter_view.subviews
794  filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object,
795  NSDictionary* bindings) {
796  return [desired_platform_subviews_set containsObject:object];
797  }]];
798  // Manipulate view hierarchy only if needed, to address a performance issue where
799  // `BringLayersIntoView` is called even when view hierarchy stays the same.
800  // See: https://github.com/flutter/flutter/issues/121833
801  // TODO(hellohuanlin): investigate if it is possible to skip unnecessary BringLayersIntoView.
802  if (![desired_platform_subviews isEqualToArray:existing_platform_subviews]) {
803  for (UIView* subview in desired_platform_subviews) {
804  // `addSubview` will automatically reorder subview if it is already added.
805  [flutter_view addSubview:subview];
806  }
807  }
808 }
809 
810 std::shared_ptr<FlutterPlatformViewLayer> FlutterPlatformViewsController::GetLayer(
811  GrDirectContext* gr_context,
812  const std::shared_ptr<IOSContext>& ios_context,
813  EmbedderViewSlice* slice,
814  SkIRect rect,
815  int64_t view_id,
816  int64_t overlay_id,
817  MTLPixelFormat pixel_format) {
818  FML_DCHECK(flutter_view_);
819  std::shared_ptr<FlutterPlatformViewLayer> layer =
820  layer_pool_->GetLayer(gr_context, ios_context, pixel_format);
821 
822  UIView* overlay_view_wrapper = layer->overlay_view_wrapper.get();
823  auto screenScale = [UIScreen mainScreen].scale;
824  // Set the size of the overlay view wrapper.
825  // This wrapper view masks the overlay view.
826  overlay_view_wrapper.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale,
827  rect.width() / screenScale, rect.height() / screenScale);
828  // Set a unique view identifier, so the overlay_view_wrapper can be identified in XCUITests.
829  overlay_view_wrapper.accessibilityIdentifier =
830  [NSString stringWithFormat:@"platform_view[%lld].overlay[%lld]", view_id, overlay_id];
831 
832  UIView* overlay_view = layer->overlay_view.get();
833  // Set the size of the overlay view.
834  // This size is equal to the device screen size.
835  overlay_view.frame = [flutter_view_.get() convertRect:flutter_view_.get().bounds
836  toView:overlay_view_wrapper];
837  // Set a unique view identifier, so the overlay_view can be identified in XCUITests.
838  overlay_view.accessibilityIdentifier =
839  [NSString stringWithFormat:@"platform_view[%lld].overlay_view[%lld]", view_id, overlay_id];
840 
841  std::unique_ptr<SurfaceFrame> frame = layer->surface->AcquireFrame(frame_size_);
842  // If frame is null, AcquireFrame already printed out an error message.
843  if (!frame) {
844  return layer;
845  }
846  DlCanvas* overlay_canvas = frame->Canvas();
847  int restore_count = overlay_canvas->GetSaveCount();
848  overlay_canvas->Save();
849  overlay_canvas->ClipRect(SkRect::Make(rect));
850  overlay_canvas->Clear(DlColor::kTransparent());
851  slice->render_into(overlay_canvas);
852  overlay_canvas->RestoreToCount(restore_count);
853 
854  layer->did_submit_last_frame = frame->Submit();
855  return layer;
856 }
857 
858 void FlutterPlatformViewsController::RemoveUnusedLayers() {
859  std::vector<std::shared_ptr<FlutterPlatformViewLayer>> layers = layer_pool_->GetUnusedLayers();
860  for (const std::shared_ptr<FlutterPlatformViewLayer>& layer : layers) {
861  [layer->overlay_view_wrapper removeFromSuperview];
862  }
863 
864  std::unordered_set<int64_t> composition_order_set;
865  for (int64_t view_id : composition_order_) {
866  composition_order_set.insert(view_id);
867  }
868  // Remove unused platform views.
869  for (int64_t view_id : active_composition_order_) {
870  if (composition_order_set.find(view_id) == composition_order_set.end()) {
871  UIView* platform_view_root = root_views_[view_id].get();
872  [platform_view_root removeFromSuperview];
873  }
874  }
875 }
876 
877 void FlutterPlatformViewsController::DisposeViews() {
878  if (views_to_dispose_.empty()) {
879  return;
880  }
881 
882  FML_DCHECK([[NSThread currentThread] isMainThread]);
883 
884  std::unordered_set<int64_t> views_to_composite(composition_order_.begin(),
885  composition_order_.end());
886  std::unordered_set<int64_t> views_to_delay_dispose;
887  for (int64_t viewId : views_to_dispose_) {
888  if (views_to_composite.count(viewId)) {
889  views_to_delay_dispose.insert(viewId);
890  continue;
891  }
892  UIView* root_view = root_views_[viewId].get();
893  [root_view removeFromSuperview];
894  views_.erase(viewId);
895  touch_interceptors_.erase(viewId);
896  root_views_.erase(viewId);
897  current_composition_params_.erase(viewId);
898  clip_count_.erase(viewId);
899  views_to_recomposite_.erase(viewId);
900  }
901 
902  views_to_dispose_ = std::move(views_to_delay_dispose);
903 }
904 
905 void FlutterPlatformViewsController::BeginCATransaction() {
906  FML_DCHECK([[NSThread currentThread] isMainThread]);
907  FML_DCHECK(!catransaction_added_);
908  [CATransaction begin];
909  catransaction_added_ = true;
910 }
911 
912 void FlutterPlatformViewsController::CommitCATransactionIfNeeded() {
913  if (catransaction_added_) {
914  FML_DCHECK([[NSThread currentThread] isMainThread]);
915  [CATransaction commit];
916  catransaction_added_ = false;
917  }
918 }
919 
920 void FlutterPlatformViewsController::ResetFrameState() {
921  slices_.clear();
922  composition_order_.clear();
923  visited_platform_views_.clear();
924 }
925 
926 } // namespace flutter
927 
928 // This recognizers delays touch events from being dispatched to the responder chain until it failed
929 // recognizing a gesture.
930 //
931 // We only fail this recognizer when asked to do so by the Flutter framework (which does so by
932 // invoking an acceptGesture method on the platform_views channel). And this is how we allow the
933 // Flutter framework to delay or prevent the embedded view from getting a touch sequence.
934 @interface DelayingGestureRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>
935 
936 // Indicates that if the `DelayingGestureRecognizer`'s state should be set to
937 // `UIGestureRecognizerStateEnded` during next `touchesEnded` call.
938 @property(nonatomic) bool shouldEndInNextTouchesEnded;
939 
940 // Indicates that the `DelayingGestureRecognizer`'s `touchesEnded` has been invoked without
941 // setting the state to `UIGestureRecognizerStateEnded`.
942 @property(nonatomic) bool touchedEndedWithoutBlocking;
943 
944 - (instancetype)initWithTarget:(id)target
945  action:(SEL)action
946  forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer;
947 @end
948 
949 // While the DelayingGestureRecognizer is preventing touches from hitting the responder chain
950 // the touch events are not arriving to the FlutterView (and thus not arriving to the Flutter
951 // framework). We use this gesture recognizer to dispatch the events directly to the FlutterView
952 // while during this phase.
953 //
954 // If the Flutter framework decides to dispatch events to the embedded view, we fail the
955 // DelayingGestureRecognizer which sends the events up the responder chain. But since the events
956 // are handled by the embedded view they are not delivered to the Flutter framework in this phase
957 // as well. So during this phase as well the ForwardingGestureRecognizer dispatched the events
958 // directly to the FlutterView.
959 @interface ForwardingGestureRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>
960 - (instancetype)initWithTarget:(id)target
961  platformViewsController:
962  (fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController;
963 @end
964 
966  fml::scoped_nsobject<DelayingGestureRecognizer> _delayingRecognizer;
968  UIView* _embeddedView;
969  // The used as the accessiblityContainer.
970  // The `accessiblityContainer` is used in UIKit to determine the parent of this accessibility
971  // node.
973 }
974 - (instancetype)initWithEmbeddedView:(UIView*)embeddedView
975  platformViewsController:
976  (fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController
977  gestureRecognizersBlockingPolicy:
979  self = [super initWithFrame:embeddedView.frame];
980  if (self) {
981  self.multipleTouchEnabled = YES;
982  _embeddedView = embeddedView;
983  embeddedView.autoresizingMask =
984  (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
985 
986  [self addSubview:embeddedView];
987 
988  ForwardingGestureRecognizer* forwardingRecognizer = [[[ForwardingGestureRecognizer alloc]
989  initWithTarget:self
990  platformViewsController:std::move(platformViewsController)] autorelease];
991 
992  _delayingRecognizer.reset([[DelayingGestureRecognizer alloc]
993  initWithTarget:self
994  action:nil
995  forwardingRecognizer:forwardingRecognizer]);
996  _blockingPolicy = blockingPolicy;
997 
998  [self addGestureRecognizer:_delayingRecognizer.get()];
999  [self addGestureRecognizer:forwardingRecognizer];
1000  }
1001  return self;
1002 }
1003 
1004 - (UIView*)embeddedView {
1005  return [[_embeddedView retain] autorelease];
1006 }
1007 
1008 - (void)releaseGesture {
1009  _delayingRecognizer.get().state = UIGestureRecognizerStateFailed;
1010 }
1011 
1012 - (void)blockGesture {
1013  switch (_blockingPolicy) {
1015  // We block all other gesture recognizers immediately in this policy.
1016  _delayingRecognizer.get().state = UIGestureRecognizerStateEnded;
1017  break;
1019  if (_delayingRecognizer.get().touchedEndedWithoutBlocking) {
1020  // If touchesEnded of the `DelayingGesureRecognizer` has been already invoked,
1021  // we want to set the state of the `DelayingGesureRecognizer` to
1022  // `UIGestureRecognizerStateEnded` as soon as possible.
1023  _delayingRecognizer.get().state = UIGestureRecognizerStateEnded;
1024  } else {
1025  // If touchesEnded of the `DelayingGesureRecognizer` has not been invoked,
1026  // We will set a flag to notify the `DelayingGesureRecognizer` to set the state to
1027  // `UIGestureRecognizerStateEnded` when touchesEnded is called.
1028  _delayingRecognizer.get().shouldEndInNextTouchesEnded = YES;
1029  }
1030  break;
1031  default:
1032  break;
1033  }
1034 }
1035 
1036 // We want the intercepting view to consume the touches and not pass the touches up to the parent
1037 // view. Make the touch event method not call super will not pass the touches up to the parent view.
1038 // Hence we overide the touch event methods and do nothing.
1039 - (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
1040 }
1041 
1042 - (void)touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
1043 }
1044 
1045 - (void)touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
1046 }
1047 
1048 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1049 }
1050 
1051 - (void)setFlutterAccessibilityContainer:(NSObject*)flutterAccessibilityContainer {
1052  _flutterAccessibilityContainer = flutterAccessibilityContainer;
1053 }
1054 
1055 - (id)accessibilityContainer {
1057 }
1058 
1059 @end
1060 
1061 @implementation DelayingGestureRecognizer {
1062  fml::scoped_nsobject<UIGestureRecognizer> _forwardingRecognizer;
1063 }
1064 
1065 - (instancetype)initWithTarget:(id)target
1066  action:(SEL)action
1067  forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer {
1068  self = [super initWithTarget:target action:action];
1069  if (self) {
1070  self.delaysTouchesBegan = YES;
1071  self.delaysTouchesEnded = YES;
1072  self.delegate = self;
1073  self.shouldEndInNextTouchesEnded = NO;
1074  self.touchedEndedWithoutBlocking = NO;
1075  _forwardingRecognizer.reset([forwardingRecognizer retain]);
1076  }
1077  return self;
1078 }
1079 
1080 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
1081  shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
1082  // The forwarding gesture recognizer should always get all touch events, so it should not be
1083  // required to fail by any other gesture recognizer.
1084  return otherGestureRecognizer != _forwardingRecognizer.get() && otherGestureRecognizer != self;
1085 }
1086 
1087 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
1088  shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
1089  return otherGestureRecognizer == self;
1090 }
1091 
1092 - (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
1093  self.touchedEndedWithoutBlocking = NO;
1094  [super touchesBegan:touches withEvent:event];
1095 }
1096 
1097 - (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
1098  if (self.shouldEndInNextTouchesEnded) {
1099  self.state = UIGestureRecognizerStateEnded;
1100  self.shouldEndInNextTouchesEnded = NO;
1101  } else {
1102  self.touchedEndedWithoutBlocking = YES;
1103  }
1104  [super touchesEnded:touches withEvent:event];
1105 }
1106 
1107 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1108  self.state = UIGestureRecognizerStateFailed;
1109 }
1110 @end
1111 
1113  // Weak reference to FlutterPlatformViewsController. The FlutterPlatformViewsController has
1114  // a reference to the FlutterViewController, where we can dispatch pointer events to.
1115  //
1116  // The lifecycle of FlutterPlatformViewsController is bind to FlutterEngine, which should always
1117  // outlives the FlutterViewController. And ForwardingGestureRecognizer is owned by a subview of
1118  // FlutterView, so the ForwardingGestureRecognizer never out lives FlutterViewController.
1119  // Therefore, `_platformViewsController` should never be nullptr.
1120  fml::WeakPtr<flutter::FlutterPlatformViewsController> _platformViewsController;
1121  // Counting the pointers that has started in one touch sequence.
1122  NSInteger _currentTouchPointersCount;
1123  // We can't dispatch events to the framework without this back pointer.
1124  // This gesture recognizer retains the `FlutterViewController` until the
1125  // end of a gesture sequence, that is all the touches in touchesBegan are concluded
1126  // with |touchesCancelled| or |touchesEnded|.
1127  fml::scoped_nsobject<UIViewController> _flutterViewController;
1128 }
1129 
1130 - (instancetype)initWithTarget:(id)target
1131  platformViewsController:
1132  (fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController {
1133  self = [super initWithTarget:target action:nil];
1134  if (self) {
1135  self.delegate = self;
1136  FML_DCHECK(platformViewsController.get() != nullptr);
1137  _platformViewsController = std::move(platformViewsController);
1139  }
1140  return self;
1141 }
1142 
1143 - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1144  FML_DCHECK(_currentTouchPointersCount >= 0);
1145  if (_currentTouchPointersCount == 0) {
1146  // At the start of each gesture sequence, we reset the `_flutterViewController`,
1147  // so that all the touch events in the same sequence are forwarded to the same
1148  // `_flutterViewController`.
1149  _flutterViewController.reset([_platformViewsController->getFlutterViewController() retain]);
1150  }
1151  [_flutterViewController.get() touchesBegan:touches withEvent:event];
1152  _currentTouchPointersCount += touches.count;
1153 }
1154 
1155 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1156  [_flutterViewController.get() touchesMoved:touches withEvent:event];
1157 }
1158 
1159 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1160  [_flutterViewController.get() touchesEnded:touches withEvent:event];
1161  _currentTouchPointersCount -= touches.count;
1162  // Touches in one touch sequence are sent to the touchesEnded method separately if different
1163  // fingers stop touching the screen at different time. So one touchesEnded method triggering does
1164  // not necessarially mean the touch sequence has ended. We Only set the state to
1165  // UIGestureRecognizerStateFailed when all the touches in the current touch sequence is ended.
1166  if (_currentTouchPointersCount == 0) {
1167  self.state = UIGestureRecognizerStateFailed;
1168  _flutterViewController.reset(nil);
1169  }
1170 }
1171 
1172 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1173  // In the event of platform view is removed, iOS generates a "stationary" change type instead of
1174  // "cancelled" change type.
1175  // Flutter needs all the cancelled touches to be "cancelled" change types in order to correctly
1176  // handle gesture sequence.
1177  // We always override the change type to "cancelled".
1179  _currentTouchPointersCount -= touches.count;
1180  if (_currentTouchPointersCount == 0) {
1181  self.state = UIGestureRecognizerStateFailed;
1182  _flutterViewController.reset(nil);
1183  }
1184 }
1185 
1186 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
1187  shouldRecognizeSimultaneouslyWithGestureRecognizer:
1188  (UIGestureRecognizer*)otherGestureRecognizer {
1189  return YES;
1190 }
1191 @end
DelayingGestureRecognizer::touchedEndedWithoutBlocking
bool touchedEndedWithoutBlocking
Definition: FlutterPlatformViews.mm:942
UIView(FirstResponder)::flt_hasFirstResponderInViewHierarchySubtree
BOOL flt_hasFirstResponderInViewHierarchySubtree
Definition: FlutterPlatformViews_Internal.h:444
FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded
@ FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded
Definition: FlutterPlugin.h:269
FlutterViewController
Definition: FlutterViewController.h:56
-[FlutterTouchInterceptingView blockGesture]
void blockGesture()
Definition: FlutterPlatformViews.mm:1012
FlutterMethodNotImplemented
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
_flutterViewController
fml::scoped_nsobject< UIViewController > _flutterViewController
Definition: FlutterPlatformViews.mm:1127
-[FlutterTouchInterceptingView releaseGesture]
void releaseGesture()
Definition: FlutterPlatformViews.mm:1008
flutter::IOSSurface::CreateGPUSurface
virtual std::unique_ptr< Surface > CreateGPUSurface(GrDirectContext *gr_context=nullptr)=0
_currentTouchPointersCount
NSInteger _currentTouchPointersCount
Definition: FlutterPlatformViews.mm:1112
FlutterError
Definition: FlutterCodecs.h:246
ForwardingGestureRecognizer
Definition: FlutterPlatformViews.mm:959
FlutterChannels.h
-[FlutterTouchInterceptingView embeddedView]
UIView * embeddedView()
Definition: FlutterPlatformViews.mm:1004
-[ChildClippingView applyBlurBackdropFilters:]
void applyBlurBackdropFilters:(NSArray< PlatformViewFilter * > *filters)
Definition: FlutterPlatformViews_Internal.mm:203
flutter::canApplyBlurBackdrop
BOOL canApplyBlurBackdrop
Definition: FlutterPlatformViews.mm:86
flutter::ResetAnchor
void ResetAnchor(CALayer *layer)
Definition: FlutterPlatformViews_Internal.mm:56
platform_view
std::unique_ptr< flutter::PlatformViewIOS > platform_view
Definition: FlutterEnginePlatformViewTest.mm:61
_blockingPolicy
FlutterPlatformViewGestureRecognizersBlockingPolicy _blockingPolicy
Definition: FlutterPlatformViews.mm:965
_flutterAccessibilityContainer
NSObject * _flutterAccessibilityContainer
Definition: FlutterPlatformViews.mm:972
flutter::IOSSurface
Definition: ios_surface.h:25
_platformViewsController
std::shared_ptr< flutter::FlutterPlatformViewsController > _platformViewsController
Definition: FlutterEngine.mm:125
ios_surface.h
FlutterMethodCall
Definition: FlutterCodecs.h:220
FlutterPlatformViewGestureRecognizersBlockingPolicyEager
@ FlutterPlatformViewGestureRecognizersBlockingPolicyEager
Definition: FlutterPlugin.h:261
flutter
Definition: accessibility_bridge.h:28
-[flutter::FlutterPlatformViewsController GetFlutterTouchInterceptingViewByID]
FlutterTouchInterceptingView * GetFlutterTouchInterceptingViewByID(int64_t view_id)
Definition: FlutterPlatformViews.mm:418
FlutterOverlayView.h
FlutterPlatformViews_Internal.h
FlutterResult
void(^ FlutterResult)(id _Nullable result)
Definition: FlutterChannels.h:194
FlutterPlatformViewGestureRecognizersBlockingPolicy
FlutterPlatformViewGestureRecognizersBlockingPolicy
Definition: FlutterPlugin.h:252
DelayingGestureRecognizer
Definition: FlutterPlatformViews.mm:934
UIView(FirstResponder)
Definition: FlutterPlatformViews.mm:22
ChildClippingView
Definition: FlutterPlatformViews_Internal.h:117
FlutterOverlayView
Definition: FlutterOverlayView.h:31
FlutterViewController_Internal.h
FlutterStandardTypedData
Definition: FlutterCodecs.h:300
ClipRRectContainsPlatformViewBoundingRect
static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect &clip_rrect, const SkRect &platformview_boundingrect, const SkMatrix &transform_matrix)
Definition: FlutterPlatformViews.mm:59
FlutterView
Definition: FlutterView.h:39
FlutterTouchInterceptingView
Definition: FlutterPlatformViews.mm:965
flutter::GetCATransform3DFromSkMatrix
CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix &matrix)
Definition: FlutterPlatformViews_Internal.mm:41
flutter::GetCGRectFromSkRect
CGRect GetCGRectFromSkRect(const SkRect &clipSkRect)
Definition: FlutterPlatformViews_Internal.mm:62
PlatformViewFilter
Definition: FlutterPlatformViews_Internal.h:78
DelayingGestureRecognizer::shouldEndInNextTouchesEnded
bool shouldEndInNextTouchesEnded
Definition: FlutterPlatformViews.mm:938
FlutterView.h
ClipRectContainsPlatformViewBoundingRect
static bool ClipRectContainsPlatformViewBoundingRect(const SkRect &clip_rect, const SkRect &platformview_boundingrect, const SkMatrix &transform_matrix)
Definition: FlutterPlatformViews.mm:44
FlutterMethodCall::arguments
id arguments
Definition: FlutterCodecs.h:238
FlutterClippingMaskView
Definition: FlutterPlatformViews_Internal.h:29
_embeddedView
UIView * _embeddedView
Definition: FlutterPlatformViews.mm:968