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