Flutter Engine
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/flow/rtree.h"
14 #include "flutter/fml/platform/darwin/scoped_nsobject.h"
15 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
16 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h"
17 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
18 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
19 #import "flutter/shell/platform/darwin/ios/ios_surface.h"
20 #import "flutter/shell/platform/darwin/ios/ios_surface_gl.h"
21 
22 namespace flutter {
23 
24 std::shared_ptr<FlutterPlatformViewLayer> FlutterPlatformViewLayerPool::GetLayer(
25  GrDirectContext* gr_context,
26  std::shared_ptr<IOSContext> ios_context) {
27  if (available_layer_index_ >= layers_.size()) {
28  std::shared_ptr<FlutterPlatformViewLayer> layer;
30  fml::scoped_nsobject<FlutterOverlayView> overlay_view_wrapper;
31 
32  if (!gr_context) {
33  overlay_view.reset([[FlutterOverlayView alloc] init]);
34  overlay_view_wrapper.reset([[FlutterOverlayView alloc] init]);
35 
36  auto ca_layer = fml::scoped_nsobject<CALayer>{[[overlay_view.get() layer] retain]};
37  std::unique_ptr<IOSSurface> ios_surface = IOSSurface::Create(ios_context, ca_layer);
38  std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface();
39 
40  layer = std::make_shared<FlutterPlatformViewLayer>(
41  std::move(overlay_view), std::move(overlay_view_wrapper), std::move(ios_surface),
42  std::move(surface));
43  } else {
44  CGFloat screenScale = [UIScreen mainScreen].scale;
45  overlay_view.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale]);
46  overlay_view_wrapper.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale]);
47 
48  auto ca_layer = fml::scoped_nsobject<CALayer>{[[overlay_view.get() layer] retain]};
49  std::unique_ptr<IOSSurface> ios_surface = IOSSurface::Create(ios_context, ca_layer);
50  std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface(gr_context);
51 
52  layer = std::make_shared<FlutterPlatformViewLayer>(
53  std::move(overlay_view), std::move(overlay_view_wrapper), std::move(ios_surface),
54  std::move(surface));
55  layer->gr_context = gr_context;
56  }
57  // The overlay view wrapper masks the overlay view.
58  // This is required to keep the backing surface size unchanged between frames.
59  //
60  // Otherwise, changing the size of the overlay would require a new surface,
61  // which can be very expensive.
62  //
63  // This is the case of an animation in which the overlay size is changing in every frame.
64  //
65  // +------------------------+
66  // | overlay_view |
67  // | +--------------+ | +--------------+
68  // | | wrapper | | == mask => | overlay_view |
69  // | +--------------+ | +--------------+
70  // +------------------------+
71  overlay_view_wrapper.get().clipsToBounds = YES;
72  [overlay_view_wrapper.get() addSubview:overlay_view];
73  layers_.push_back(layer);
74  }
75  std::shared_ptr<FlutterPlatformViewLayer> layer = layers_[available_layer_index_];
76  if (gr_context != layer->gr_context) {
77  layer->gr_context = gr_context;
78  // The overlay already exists, but the GrContext was changed so we need to recreate
79  // the rendering surface with the new GrContext.
80  IOSSurface* ios_surface = layer->ios_surface.get();
81  std::unique_ptr<Surface> surface = ios_surface->CreateGPUSurface(gr_context);
82  layer->surface = std::move(surface);
83  }
84  available_layer_index_++;
85  return layer;
86 }
87 
89  available_layer_index_ = 0;
90 }
91 
92 std::vector<std::shared_ptr<FlutterPlatformViewLayer>>
94  std::vector<std::shared_ptr<FlutterPlatformViewLayer>> results;
95  for (size_t i = available_layer_index_; i < layers_.size(); i++) {
96  results.push_back(layers_[i]);
97  }
98  return results;
99 }
100 
102  flutter_view_.reset([flutter_view retain]);
103 }
104 
106  UIViewController* flutter_view_controller) {
107  flutter_view_controller_.reset([flutter_view_controller retain]);
108 }
109 
111  return flutter_view_controller_.get();
112 }
113 
115  if ([[call method] isEqualToString:@"create"]) {
116  OnCreate(call, result);
117  } else if ([[call method] isEqualToString:@"dispose"]) {
118  OnDispose(call, result);
119  } else if ([[call method] isEqualToString:@"acceptGesture"]) {
120  OnAcceptGesture(call, result);
121  } else if ([[call method] isEqualToString:@"rejectGesture"]) {
122  OnRejectGesture(call, result);
123  } else {
125  }
126 }
127 
128 void FlutterPlatformViewsController::OnCreate(FlutterMethodCall* call, FlutterResult& result) {
129  NSDictionary<NSString*, id>* args = [call arguments];
130 
131  long viewId = [args[@"id"] longValue];
132  std::string viewType([args[@"viewType"] UTF8String]);
133 
134  if (views_.count(viewId) != 0) {
135  result([FlutterError errorWithCode:@"recreating_view"
136  message:@"trying to create an already created view"
137  details:[NSString stringWithFormat:@"view id: '%ld'", viewId]]);
138  }
139 
140  NSObject<FlutterPlatformViewFactory>* factory = factories_[viewType].get();
141  if (factory == nil) {
142  result([FlutterError errorWithCode:@"unregistered_view_type"
143  message:@"trying to create a view with an unregistered type"
144  details:[NSString stringWithFormat:@"unregistered view type: '%@'",
145  args[@"viewType"]]]);
146  return;
147  }
148 
149  id params = nil;
150  if ([factory respondsToSelector:@selector(createArgsCodec)]) {
151  NSObject<FlutterMessageCodec>* codec = [factory createArgsCodec];
152  if (codec != nil && args[@"params"] != nil) {
153  FlutterStandardTypedData* paramsData = args[@"params"];
154  params = [codec decode:paramsData.data];
155  }
156  }
157 
158  NSObject<FlutterPlatformView>* embedded_view = [factory createWithFrame:CGRectZero
159  viewIdentifier:viewId
160  arguments:params];
161  UIView* platform_view = [embedded_view view];
162  // Set a unique view identifier, so the platform view can be identified in unit tests.
163  platform_view.accessibilityIdentifier = [NSString stringWithFormat:@"platform_view[%ld]", viewId];
164  views_[viewId] = fml::scoped_nsobject<NSObject<FlutterPlatformView>>([embedded_view retain]);
165 
166  FlutterTouchInterceptingView* touch_interceptor = [[[FlutterTouchInterceptingView alloc]
167  initWithEmbeddedView:platform_view
168  platformViewsController:GetWeakPtr()
169  gestureRecognizersBlockingPolicy:gesture_recognizers_blocking_policies[viewType]]
170  autorelease];
171 
172  touch_interceptors_[viewId] =
173  fml::scoped_nsobject<FlutterTouchInterceptingView>([touch_interceptor retain]);
174 
175  ChildClippingView* clipping_view =
176  [[[ChildClippingView alloc] initWithFrame:CGRectZero] autorelease];
177  [clipping_view addSubview:touch_interceptor];
178  root_views_[viewId] = fml::scoped_nsobject<UIView>([clipping_view retain]);
179 
180  result(nil);
181 }
182 
183 void FlutterPlatformViewsController::OnDispose(FlutterMethodCall* call, FlutterResult& result) {
184  NSNumber* arg = [call arguments];
185  int64_t viewId = [arg longLongValue];
186 
187  if (views_.count(viewId) == 0) {
188  result([FlutterError errorWithCode:@"unknown_view"
189  message:@"trying to dispose an unknown"
190  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
191  return;
192  }
193  // We wait for next submitFrame to dispose views.
194  views_to_dispose_.insert(viewId);
195  result(nil);
196 }
197 
198 void FlutterPlatformViewsController::OnAcceptGesture(FlutterMethodCall* call,
199  FlutterResult& result) {
200  NSDictionary<NSString*, id>* args = [call arguments];
201  int64_t viewId = [args[@"id"] longLongValue];
202 
203  if (views_.count(viewId) == 0) {
204  result([FlutterError errorWithCode:@"unknown_view"
205  message:@"trying to set gesture state for an unknown view"
206  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
207  return;
208  }
209 
210  FlutterTouchInterceptingView* view = touch_interceptors_[viewId].get();
211  [view releaseGesture];
212 
213  result(nil);
214 }
215 
216 void FlutterPlatformViewsController::OnRejectGesture(FlutterMethodCall* call,
217  FlutterResult& result) {
218  NSDictionary<NSString*, id>* args = [call arguments];
219  int64_t viewId = [args[@"id"] longLongValue];
220 
221  if (views_.count(viewId) == 0) {
222  result([FlutterError errorWithCode:@"unknown_view"
223  message:@"trying to set gesture state for an unknown view"
224  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
225  return;
226  }
227 
228  FlutterTouchInterceptingView* view = touch_interceptors_[viewId].get();
229  [view blockGesture];
230 
231  result(nil);
232 }
233 
235  NSObject<FlutterPlatformViewFactory>* factory,
236  NSString* factoryId,
237  FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy) {
238  std::string idString([factoryId UTF8String]);
239  FML_CHECK(factories_.count(idString) == 0);
240  factories_[idString] =
242  gesture_recognizers_blocking_policies[idString] = gestureRecognizerBlockingPolicy;
243 }
244 
246  ResetFrameState();
247  frame_size_ = frame_size;
248 }
249 
251  ResetFrameState();
252 }
253 
254 // TODO(cyanglaz): https://github.com/flutter/flutter/issues/56474
255 // Make this method check if there are pending view operations instead.
256 // Also rename it to `HasPendingViewOperations`.
257 bool FlutterPlatformViewsController::HasPlatformViewThisOrNextFrame() {
258  return composition_order_.size() > 0 || active_composition_order_.size() > 0;
259 }
260 
261 const int FlutterPlatformViewsController::kDefaultMergedLeaseDuration;
262 
264  fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
265  // TODO(cyanglaz): https://github.com/flutter/flutter/issues/56474
266  // Rename `has_platform_view` to `view_mutated` when the above issue is resolved.
267  if (!HasPlatformViewThisOrNextFrame()) {
269  }
270  if (!raster_thread_merger->IsMerged()) {
271  // The raster thread merger may be disabled if the rasterizer is being
272  // created or teared down.
273  //
274  // In such cases, the current frame is dropped, and a new frame is attempted
275  // with the same layer tree.
276  //
277  // Eventually, the frame is submitted once this method returns `kSuccess`.
278  // At that point, the raster tasks are handled on the platform thread.
279  CancelFrame();
280  raster_thread_merger->MergeWithLease(kDefaultMergedLeaseDuration);
282  }
283  // If the post preroll action is successful, we will display platform views in the current frame.
284  // In order to sync the rendering of the platform views (quartz) with skia's rendering,
285  // We need to begin an explicit CATransaction. This transaction needs to be submitted
286  // after the current frame is submitted.
287  BeginCATransaction();
288  raster_thread_merger->ExtendLeaseTo(kDefaultMergedLeaseDuration);
290 }
291 
293  int view_id,
294  std::unique_ptr<EmbeddedViewParams> params) {
295  // All the CATransactions should be committed by the end of the last frame,
296  // so catransaction_added_ must be false.
297  FML_DCHECK(!catransaction_added_);
298  picture_recorders_[view_id] = std::make_unique<SkPictureRecorder>();
299 
300  auto rtree_factory = RTreeFactory();
301  platform_view_rtrees_[view_id] = rtree_factory.getInstance();
302  picture_recorders_[view_id]->beginRecording(SkRect::Make(frame_size_), &rtree_factory);
303 
304  composition_order_.push_back(view_id);
305 
306  if (current_composition_params_.count(view_id) == 1 &&
307  current_composition_params_[view_id] == *params.get()) {
308  // Do nothing if the params didn't change.
309  return;
310  }
311  current_composition_params_[view_id] = EmbeddedViewParams(*params.get());
312  views_to_recomposite_.insert(view_id);
313 }
314 
316  if (views_.empty()) {
317  return nil;
318  }
319  return [touch_interceptors_[view_id].get() embeddedView];
320 }
321 
323  std::vector<SkCanvas*> canvases;
324  for (size_t i = 0; i < composition_order_.size(); i++) {
325  int64_t view_id = composition_order_[i];
326  canvases.push_back(picture_recorders_[view_id]->getRecordingCanvas());
327  }
328  return canvases;
329 }
330 
331 int FlutterPlatformViewsController::CountClips(const MutatorsStack& mutators_stack) {
332  std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator iter = mutators_stack.Bottom();
333  int clipCount = 0;
334  while (iter != mutators_stack.Top()) {
335  if ((*iter)->IsClipType()) {
336  clipCount++;
337  }
338  ++iter;
339  }
340  return clipCount;
341 }
342 
343 void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack,
344  UIView* embedded_view) {
345  if (flutter_view_ == nullptr) {
346  return;
347  }
348  FML_DCHECK(CATransform3DEqualToTransform(embedded_view.layer.transform, CATransform3DIdentity));
349  ResetAnchor(embedded_view.layer);
350  ChildClippingView* clipView = (ChildClippingView*)embedded_view.superview;
351 
352  // The UIKit frame is set based on the logical resolution instead of physical.
353  // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html).
354  // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals
355  // 500 points in UIKit. And until this point, we did all the calculation based on the flow
356  // resolution. So we need to scale down to match UIKit's logical resolution.
357  CGFloat screenScale = [UIScreen mainScreen].scale;
358  CATransform3D finalTransform = CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1);
359 
360  UIView* flutter_view = flutter_view_.get();
362  initWithFrame:CGRectMake(-clipView.frame.origin.x, -clipView.frame.origin.y,
363  CGRectGetWidth(flutter_view.bounds),
364  CGRectGetHeight(flutter_view.bounds))] autorelease];
365 
366  auto iter = mutators_stack.Begin();
367  while (iter != mutators_stack.End()) {
368  switch ((*iter)->GetType()) {
369  case transform: {
370  CATransform3D transform = GetCATransform3DFromSkMatrix((*iter)->GetMatrix());
371  finalTransform = CATransform3DConcat(transform, finalTransform);
372  break;
373  }
374  case clip_rect:
375  [maskView clipRect:(*iter)->GetRect() matrix:finalTransform];
376  break;
377  case clip_rrect:
378  [maskView clipRRect:(*iter)->GetRRect() matrix:finalTransform];
379  break;
380  case clip_path:
381  [maskView clipPath:(*iter)->GetPath() matrix:finalTransform];
382  break;
383  case opacity:
384  embedded_view.alpha = (*iter)->GetAlphaFloat() * embedded_view.alpha;
385  break;
386  }
387  ++iter;
388  }
389  // Reverse the offset of the clipView.
390  // The clipView's frame includes the final translate of the final transform matrix.
391  // So we need to revese this translate so the platform view can layout at the correct offset.
392  //
393  // Note that we don't apply this transform matrix the clippings because clippings happen on the
394  // mask view, whose origin is always (0,0) to the flutter_view.
395  CATransform3D reverseTranslate =
396  CATransform3DMakeTranslation(-clipView.frame.origin.x, -clipView.frame.origin.y, 0);
397  embedded_view.layer.transform = CATransform3DConcat(finalTransform, reverseTranslate);
398  clipView.maskView = maskView;
399 }
400 
401 void FlutterPlatformViewsController::CompositeWithParams(int view_id,
402  const EmbeddedViewParams& params) {
403  CGRect frame = CGRectMake(0, 0, params.sizePoints().width(), params.sizePoints().height());
404  UIView* touchInterceptor = touch_interceptors_[view_id].get();
405  touchInterceptor.layer.transform = CATransform3DIdentity;
406  touchInterceptor.frame = frame;
407  touchInterceptor.alpha = 1;
408 
409  const MutatorsStack& mutatorStack = params.mutatorsStack();
410  UIView* clippingView = root_views_[view_id].get();
411  // The frame of the clipping view should be the final bounding rect.
412  // Because the translate matrix in the Mutator Stack also includes the offset,
413  // when we apply the transforms matrix in |ApplyMutators|, we need
414  // to remember to do a reverse translate.
415  const SkRect& rect = params.finalBoundingRect();
416  CGFloat screenScale = [UIScreen mainScreen].scale;
417  clippingView.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale,
418  rect.width() / screenScale, rect.height() / screenScale);
419  ApplyMutators(mutatorStack, touchInterceptor);
420 }
421 
423  // Any UIKit related code has to run on main thread.
424  FML_DCHECK([[NSThread currentThread] isMainThread]);
425  // Do nothing if the view doesn't need to be composited.
426  if (views_to_recomposite_.count(view_id) == 0) {
427  return picture_recorders_[view_id]->getRecordingCanvas();
428  }
429  CompositeWithParams(view_id, current_composition_params_[view_id]);
430  views_to_recomposite_.erase(view_id);
431  return picture_recorders_[view_id]->getRecordingCanvas();
432 }
433 
435  UIView* flutter_view = flutter_view_.get();
436  for (UIView* sub_view in [flutter_view subviews]) {
437  [sub_view removeFromSuperview];
438  }
439  root_views_.clear();
440  touch_interceptors_.clear();
441  views_.clear();
442  composition_order_.clear();
443  active_composition_order_.clear();
444  picture_recorders_.clear();
445  platform_view_rtrees_.clear();
446  current_composition_params_.clear();
447  clip_count_.clear();
448  views_to_recomposite_.clear();
449  layer_pool_->RecycleLayers();
450 }
451 
453  UIView* platform_view = GetPlatformViewByID(view_id);
454  UIScreen* screen = [UIScreen mainScreen];
455  CGRect platform_view_cgrect = [platform_view convertRect:platform_view.bounds
456  toView:flutter_view_];
457  return SkRect::MakeXYWH(platform_view_cgrect.origin.x * screen.scale, //
458  platform_view_cgrect.origin.y * screen.scale, //
459  platform_view_cgrect.size.width * screen.scale, //
460  platform_view_cgrect.size.height * screen.scale //
461  );
462 }
463 
464 bool FlutterPlatformViewsController::SubmitFrame(GrDirectContext* gr_context,
465  std::shared_ptr<IOSContext> ios_context,
466  std::unique_ptr<SurfaceFrame> frame) {
467  // Any UIKit related code has to run on main thread.
468  FML_DCHECK([[NSThread currentThread] isMainThread]);
469  if (flutter_view_ == nullptr) {
470  return frame->Submit();
471  }
472 
473  DisposeViews();
474 
475  SkCanvas* background_canvas = frame->SkiaCanvas();
476 
477  // Resolve all pending GPU operations before allocating a new surface.
478  background_canvas->flush();
479  // Clipping the background canvas before drawing the picture recorders requires to
480  // save and restore the clip context.
481  SkAutoCanvasRestore save(background_canvas, /*doSave=*/true);
482  // Maps a platform view id to a vector of `FlutterPlatformViewLayer`.
483  LayersMap platform_view_layers;
484 
485  auto did_submit = true;
486  auto num_platform_views = composition_order_.size();
487 
488  for (size_t i = 0; i < num_platform_views; i++) {
489  int64_t platform_view_id = composition_order_[i];
490  sk_sp<RTree> rtree = platform_view_rtrees_[platform_view_id];
491  sk_sp<SkPicture> picture = picture_recorders_[platform_view_id]->finishRecordingAsPicture();
492 
493  // Check if the current picture contains overlays that intersect with the
494  // current platform view or any of the previous platform views.
495  for (size_t j = i + 1; j > 0; j--) {
496  int64_t current_platform_view_id = composition_order_[j - 1];
497  SkRect platform_view_rect = GetPlatformViewRect(current_platform_view_id);
498  std::list<SkRect> intersection_rects =
499  rtree->searchNonOverlappingDrawnRects(platform_view_rect);
500  auto allocation_size = intersection_rects.size();
501 
502  // For testing purposes, the overlay id is used to find the overlay view.
503  // This is the index of the layer for the current platform view.
504  auto overlay_id = platform_view_layers[current_platform_view_id].size();
505 
506  // If the max number of allocations per platform view is exceeded,
507  // then join all the rects into a single one.
508  //
509  // TODO(egarciad): Consider making this configurable.
510  // https://github.com/flutter/flutter/issues/52510
511  if (allocation_size > kMaxLayerAllocations) {
512  SkRect joined_rect;
513  for (const SkRect& rect : intersection_rects) {
514  joined_rect.join(rect);
515  }
516  // Replace the rects in the intersection rects list for a single rect that is
517  // the union of all the rects in the list.
518  intersection_rects.clear();
519  intersection_rects.push_back(joined_rect);
520  }
521  for (SkRect& joined_rect : intersection_rects) {
522  // Get the intersection rect between the current rect
523  // and the platform view rect.
524  joined_rect.intersect(platform_view_rect);
525  // Subpixels in the platform may not align with the canvas subpixels.
526  // To workaround it, round the floating point bounds and make the rect slightly larger.
527  // For example, {0.3, 0.5, 3.1, 4.7} becomes {0, 0, 4, 5}.
528  joined_rect.setLTRB(std::floor(joined_rect.left()), std::floor(joined_rect.top()),
529  std::ceil(joined_rect.right()), std::ceil(joined_rect.bottom()));
530  // Clip the background canvas, so it doesn't contain any of the pixels drawn
531  // on the overlay layer.
532  background_canvas->clipRect(joined_rect, SkClipOp::kDifference);
533  // Get a new host layer.
534  std::shared_ptr<FlutterPlatformViewLayer> layer = GetLayer(gr_context, //
535  ios_context, //
536  picture, //
537  joined_rect, //
538  current_platform_view_id, //
539  overlay_id //
540  );
541  did_submit &= layer->did_submit_last_frame;
542  platform_view_layers[current_platform_view_id].push_back(layer);
543  overlay_id++;
544  }
545  }
546  background_canvas->drawPicture(picture);
547  }
548  // If a layer was allocated in the previous frame, but it's not used in the current frame,
549  // then it can be removed from the scene.
550  RemoveUnusedLayers();
551  // Organize the layers by their z indexes.
552  BringLayersIntoView(platform_view_layers);
553  // Mark all layers as available, so they can be used in the next frame.
554  layer_pool_->RecycleLayers();
555 
556  did_submit &= frame->Submit();
557 
558  // If the frame is submitted with embedded platform views,
559  // there should be a |[CATransaction begin]| call in this frame prior to all the drawing.
560  // If that case, we need to commit the transaction.
561  CommitCATransactionIfNeeded();
562  return did_submit;
563 }
564 
565 void FlutterPlatformViewsController::BringLayersIntoView(LayersMap layer_map) {
566  FML_DCHECK(flutter_view_);
567  UIView* flutter_view = flutter_view_.get();
568  auto zIndex = 0;
569  // Clear the `active_composition_order_`, which will be populated down below.
570  active_composition_order_.clear();
571  for (size_t i = 0; i < composition_order_.size(); i++) {
572  int64_t platform_view_id = composition_order_[i];
573  std::vector<std::shared_ptr<FlutterPlatformViewLayer>> layers = layer_map[platform_view_id];
574  UIView* platform_view_root = root_views_[platform_view_id].get();
575 
576  if (platform_view_root.superview != flutter_view) {
577  [flutter_view addSubview:platform_view_root];
578  } else {
579  platform_view_root.layer.zPosition = zIndex++;
580  }
581  for (const std::shared_ptr<FlutterPlatformViewLayer>& layer : layers) {
582  if ([layer->overlay_view_wrapper superview] != flutter_view) {
583  [flutter_view addSubview:layer->overlay_view_wrapper];
584  } else {
585  layer->overlay_view_wrapper.get().layer.zPosition = zIndex++;
586  }
587  }
588  active_composition_order_.push_back(platform_view_id);
589  }
590 }
591 
592 std::shared_ptr<FlutterPlatformViewLayer> FlutterPlatformViewsController::GetLayer(
593  GrDirectContext* gr_context,
594  std::shared_ptr<IOSContext> ios_context,
595  sk_sp<SkPicture> picture,
596  SkRect rect,
597  int64_t view_id,
598  int64_t overlay_id) {
599  FML_DCHECK(flutter_view_);
600  std::shared_ptr<FlutterPlatformViewLayer> layer = layer_pool_->GetLayer(gr_context, ios_context);
601 
602  UIView* overlay_view_wrapper = layer->overlay_view_wrapper.get();
603  auto screenScale = [UIScreen mainScreen].scale;
604  // Set the size of the overlay view wrapper.
605  // This wrapper view masks the overlay view.
606  overlay_view_wrapper.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale,
607  rect.width() / screenScale, rect.height() / screenScale);
608  // Set a unique view identifier, so the overlay wrapper can be identified in unit tests.
609  overlay_view_wrapper.accessibilityIdentifier =
610  [NSString stringWithFormat:@"platform_view[%lld].overlay[%lld]", view_id, overlay_id];
611 
612  UIView* overlay_view = layer->overlay_view.get();
613  // Set the size of the overlay view.
614  // This size is equal to the the device screen size.
615  overlay_view.frame = flutter_view_.get().bounds;
616 
617  std::unique_ptr<SurfaceFrame> frame = layer->surface->AcquireFrame(frame_size_);
618  // If frame is null, AcquireFrame already printed out an error message.
619  if (!frame) {
620  return layer;
621  }
622  SkCanvas* overlay_canvas = frame->SkiaCanvas();
623  overlay_canvas->clear(SK_ColorTRANSPARENT);
624  // Offset the picture since its absolute position on the scene is determined
625  // by the position of the overlay view.
626  overlay_canvas->translate(-rect.x(), -rect.y());
627  overlay_canvas->drawPicture(picture);
628 
629  layer->did_submit_last_frame = frame->Submit();
630  return layer;
631 }
632 
633 void FlutterPlatformViewsController::RemoveUnusedLayers() {
634  std::vector<std::shared_ptr<FlutterPlatformViewLayer>> layers = layer_pool_->GetUnusedLayers();
635  for (const std::shared_ptr<FlutterPlatformViewLayer>& layer : layers) {
636  [layer->overlay_view_wrapper removeFromSuperview];
637  }
638 
639  std::unordered_set<int64_t> composition_order_set;
640  for (int64_t view_id : composition_order_) {
641  composition_order_set.insert(view_id);
642  }
643  // Remove unused platform views.
644  for (int64_t view_id : active_composition_order_) {
645  if (composition_order_set.find(view_id) == composition_order_set.end()) {
646  UIView* platform_view_root = root_views_[view_id].get();
647  [platform_view_root removeFromSuperview];
648  }
649  }
650 }
651 
652 void FlutterPlatformViewsController::DisposeViews() {
653  if (views_to_dispose_.empty()) {
654  return;
655  }
656 
657  FML_DCHECK([[NSThread currentThread] isMainThread]);
658 
659  for (int64_t viewId : views_to_dispose_) {
660  UIView* root_view = root_views_[viewId].get();
661  [root_view removeFromSuperview];
662  views_.erase(viewId);
663  touch_interceptors_.erase(viewId);
664  root_views_.erase(viewId);
665  current_composition_params_.erase(viewId);
666  clip_count_.erase(viewId);
667  views_to_recomposite_.erase(viewId);
668  }
669  views_to_dispose_.clear();
670 }
671 
672 void FlutterPlatformViewsController::BeginCATransaction() {
673  FML_DCHECK([[NSThread currentThread] isMainThread]);
674  FML_DCHECK(!catransaction_added_);
675  [CATransaction begin];
676  catransaction_added_ = true;
677 }
678 
679 void FlutterPlatformViewsController::CommitCATransactionIfNeeded() {
680  if (catransaction_added_) {
681  FML_DCHECK([[NSThread currentThread] isMainThread]);
682  [CATransaction commit];
683  catransaction_added_ = false;
684  }
685 }
686 
687 void FlutterPlatformViewsController::ResetFrameState() {
688  picture_recorders_.clear();
689  composition_order_.clear();
690 }
691 
692 } // namespace flutter
693 
694 // This recognizers delays touch events from being dispatched to the responder chain until it failed
695 // recognizing a gesture.
696 //
697 // We only fail this recognizer when asked to do so by the Flutter framework (which does so by
698 // invoking an acceptGesture method on the platform_views channel). And this is how we allow the
699 // Flutter framework to delay or prevent the embedded view from getting a touch sequence.
700 @interface DelayingGestureRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>
701 
702 // Indicates that if the `DelayingGestureRecognizer`'s state should be set to
703 // `UIGestureRecognizerStateEnded` during next `touchesEnded` call.
704 @property(nonatomic) bool shouldEndInNextTouchesEnded;
705 
706 // Indicates that the `DelayingGestureRecognizer`'s `touchesEnded` has been invoked without
707 // setting the state to `UIGestureRecognizerStateEnded`.
708 @property(nonatomic) bool touchedEndedWithoutBlocking;
709 
710 - (instancetype)initWithTarget:(id)target
711  action:(SEL)action
712  forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer;
713 @end
714 
715 // While the DelayingGestureRecognizer is preventing touches from hitting the responder chain
716 // the touch events are not arriving to the FlutterView (and thus not arriving to the Flutter
717 // framework). We use this gesture recognizer to dispatch the events directly to the FlutterView
718 // while during this phase.
719 //
720 // If the Flutter framework decides to dispatch events to the embedded view, we fail the
721 // DelayingGestureRecognizer which sends the events up the responder chain. But since the events
722 // are handled by the embedded view they are not delivered to the Flutter framework in this phase
723 // as well. So during this phase as well the ForwardingGestureRecognizer dispatched the events
724 // directly to the FlutterView.
725 @interface ForwardingGestureRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>
726 - (instancetype)initWithTarget:(id)target
727  platformViewsController:
728  (fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController;
729 @end
730 
734  UIView* _embeddedView;
735 }
736 - (instancetype)initWithEmbeddedView:(UIView*)embeddedView
737  platformViewsController:
738  (fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController
739  gestureRecognizersBlockingPolicy:
741  self = [super initWithFrame:embeddedView.frame];
742  if (self) {
743  self.multipleTouchEnabled = YES;
744  _embeddedView = embeddedView;
745  embeddedView.autoresizingMask =
746  (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
747 
748  [self addSubview:embeddedView];
749 
750  ForwardingGestureRecognizer* forwardingRecognizer = [[[ForwardingGestureRecognizer alloc]
751  initWithTarget:self
752  platformViewsController:std::move(platformViewsController)] autorelease];
753 
754  _delayingRecognizer.reset([[DelayingGestureRecognizer alloc]
755  initWithTarget:self
756  action:nil
757  forwardingRecognizer:forwardingRecognizer]);
758  _blockingPolicy = blockingPolicy;
759 
760  [self addGestureRecognizer:_delayingRecognizer.get()];
761  [self addGestureRecognizer:forwardingRecognizer];
762  }
763  return self;
764 }
765 
766 - (UIView*)embeddedView {
767  return [[_embeddedView retain] autorelease];
768 }
769 
770 - (void)releaseGesture {
771  _delayingRecognizer.get().state = UIGestureRecognizerStateFailed;
772 }
773 
774 - (void)blockGesture {
775  switch (_blockingPolicy) {
777  // We block all other gesture recognizers immediately in this policy.
778  _delayingRecognizer.get().state = UIGestureRecognizerStateEnded;
779  break;
781  if (_delayingRecognizer.get().touchedEndedWithoutBlocking) {
782  // If touchesEnded of the `DelayingGesureRecognizer` has been already invoked,
783  // we want to set the state of the `DelayingGesureRecognizer` to
784  // `UIGestureRecognizerStateEnded` as soon as possible.
785  _delayingRecognizer.get().state = UIGestureRecognizerStateEnded;
786  } else {
787  // If touchesEnded of the `DelayingGesureRecognizer` has not been invoked,
788  // We will set a flag to notify the `DelayingGesureRecognizer` to set the state to
789  // `UIGestureRecognizerStateEnded` when touchesEnded is called.
790  _delayingRecognizer.get().shouldEndInNextTouchesEnded = YES;
791  }
792  break;
793  default:
794  break;
795  }
796 }
797 
798 // We want the intercepting view to consume the touches and not pass the touches up to the parent
799 // view. Make the touch event method not call super will not pass the touches up to the parent view.
800 // Hence we overide the touch event methods and do nothing.
801 - (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
802 }
803 
804 - (void)touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
805 }
806 
807 - (void)touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
808 }
809 
810 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
811 }
812 
813 @end
814 
815 @implementation DelayingGestureRecognizer {
816  fml::scoped_nsobject<UIGestureRecognizer> _forwardingRecognizer;
817 }
818 
819 - (instancetype)initWithTarget:(id)target
820  action:(SEL)action
821  forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer {
822  self = [super initWithTarget:target action:action];
823  if (self) {
824  self.delaysTouchesBegan = YES;
825  self.delaysTouchesEnded = YES;
826  self.delegate = self;
827  self.shouldEndInNextTouchesEnded = NO;
828  self.touchedEndedWithoutBlocking = NO;
829  _forwardingRecognizer.reset([forwardingRecognizer retain]);
830  }
831  return self;
832 }
833 
834 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
835  shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
836  // The forwarding gesture recognizer should always get all touch events, so it should not be
837  // required to fail by any other gesture recognizer.
838  return otherGestureRecognizer != _forwardingRecognizer.get() && otherGestureRecognizer != self;
839 }
840 
841 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
842  shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
843  return otherGestureRecognizer == self;
844 }
845 
846 - (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
847  self.touchedEndedWithoutBlocking = NO;
848  [super touchesBegan:touches withEvent:event];
849 }
850 
851 - (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
852  if (self.shouldEndInNextTouchesEnded) {
853  self.state = UIGestureRecognizerStateEnded;
854  self.shouldEndInNextTouchesEnded = NO;
855  } else {
856  self.touchedEndedWithoutBlocking = YES;
857  }
858  [super touchesEnded:touches withEvent:event];
859 }
860 
861 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
862  self.state = UIGestureRecognizerStateFailed;
863 }
864 @end
865 
867  // Weak reference to FlutterPlatformViewsController. The FlutterPlatformViewsController has
868  // a reference to the FlutterViewController, where we can dispatch pointer events to.
869  //
870  // The lifecycle of FlutterPlatformViewsController is bind to FlutterEngine, which should always
871  // outlives the FlutterViewController. And ForwardingGestureRecognizer is owned by a subview of
872  // FlutterView, so the ForwardingGestureRecognizer never out lives FlutterViewController.
873  // Therefore, `_platformViewsController` should never be nullptr.
875  // Counting the pointers that has started in one touch sequence.
876  NSInteger _currentTouchPointersCount;
877  // We can't dispatch events to the framework without this back pointer.
878  // This gesture recognizer retains the `FlutterViewController` until the
879  // end of a gesture sequence, that is all the touches in touchesBegan are concluded
880  // with |touchesCancelled| or |touchesEnded|.
882 }
883 
884 - (instancetype)initWithTarget:(id)target
885  platformViewsController:
886  (fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController {
887  self = [super initWithTarget:target action:nil];
888  if (self) {
889  self.delegate = self;
890  FML_DCHECK(platformViewsController.get() != nullptr);
891  _platformViewsController = std::move(platformViewsController);
893  }
894  return self;
895 }
896 
897 - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
899  if (_currentTouchPointersCount == 0) {
900  // At the start of each gesture sequence, we reset the `_flutterViewController`,
901  // so that all the touch events in the same sequence are forwarded to the same
902  // `_flutterViewController`.
903  _flutterViewController.reset([_platformViewsController->getFlutterViewController() retain]);
904  }
905  [_flutterViewController.get() touchesBegan:touches withEvent:event];
906  _currentTouchPointersCount += touches.count;
907 }
908 
909 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
910  [_flutterViewController.get() touchesMoved:touches withEvent:event];
911 }
912 
913 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
914  [_flutterViewController.get() touchesEnded:touches withEvent:event];
915  _currentTouchPointersCount -= touches.count;
916  // Touches in one touch sequence are sent to the touchesEnded method separately if different
917  // fingers stop touching the screen at different time. So one touchesEnded method triggering does
918  // not necessarially mean the touch sequence has ended. We Only set the state to
919  // UIGestureRecognizerStateFailed when all the touches in the current touch sequence is ended.
920  if (_currentTouchPointersCount == 0) {
921  self.state = UIGestureRecognizerStateFailed;
923  }
924 }
925 
926 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
927  // In the event of platform view is removed, iOS generates a "stationary" change type instead of
928  // "cancelled" change type.
929  // Flutter needs all the cancelled touches to be "cancelled" change types in order to correctly
930  // handle gesture sequence.
931  // We always override the change type to "cancelled".
933  _currentTouchPointersCount -= touches.count;
934  if (_currentTouchPointersCount == 0) {
935  self.state = UIGestureRecognizerStateFailed;
937  }
938 }
939 
940 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
941  shouldRecognizeSimultaneouslyWithGestureRecognizer:
942  (UIGestureRecognizer*)otherGestureRecognizer {
943  return YES;
944 }
945 @end
G_BEGIN_DECLS FlValue * args
void RegisterViewFactory(NSObject< FlutterPlatformViewFactory > *factory, NSString *factoryId, FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy)
FlutterPlatformViewGestureRecognizersBlockingPolicy _blockingPolicy
void PrerollCompositeEmbeddedView(int view_id, std::unique_ptr< flutter::EmbeddedViewParams > params)
virtual std::unique_ptr< Surface > CreateGPUSurface(GrDirectContext *gr_context=nullptr)=0
#define FML_DCHECK(condition)
Definition: logging.h:86
std::unique_ptr< flutter::PlatformViewIOS > platform_view
auto WeakPtr(std::shared_ptr< T > pointer)
std::vector< std::shared_ptr< FlutterPlatformViewLayer > > GetUnusedLayers()
void reset(NST * object=nil)
void ResetAnchor(CALayer *layer)
GAsyncResult * result
const std::vector< std::shared_ptr< Mutator > >::const_reverse_iterator Top() const
void SetFlutterViewController(UIViewController *flutter_view_controller)
Definition: ascii_trie.cc:9
void MergeWithLease(size_t lease_term)
FlKeyEvent * event
const std::vector< std::shared_ptr< Mutator > >::const_iterator Begin() const
std::shared_ptr< flutter::FlutterPlatformViewsController > _platformViewsController
PostPrerollResult PostPrerollAction(fml::RefPtr< fml::RasterThreadMerger > raster_thread_merger)
const SkSize & sizePoints() const
const std::vector< std::shared_ptr< Mutator > >::const_iterator End() const
bool SubmitFrame(GrDirectContext *gr_context, std::shared_ptr< IOSContext > ios_context, std::unique_ptr< SurfaceFrame > frame)
void(^ FlutterResult)(id _Nullable result)
UIView * _embeddedView
CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix &matrix)
FlutterPlatformViewGestureRecognizersBlockingPolicy
int BOOL
Definition: windows_types.h:37
#define FML_CHECK(condition)
Definition: logging.h:68
FlView * view
const SkRect & finalBoundingRect() const
void OnMethodCall(FlutterMethodCall *call, FlutterResult &result)
fml::scoped_nsobject< UIViewController > _flutterViewController
const MutatorsStack & mutatorsStack() const
NSInteger _currentTouchPointersCount
static std::unique_ptr< IOSSurface > Create(std::shared_ptr< IOSContext > context, fml::scoped_nsobject< CALayer > layer)
Definition: ios_surface.mm:18
std::shared_ptr< FlutterPlatformViewLayer > GetLayer(GrDirectContext *gr_context, std::shared_ptr< IOSContext > ios_context)
void ExtendLeaseTo(size_t lease_term)
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
const std::vector< std::shared_ptr< Mutator > >::const_reverse_iterator Bottom() const