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