Flutter Engine
The 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 "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
6
7#include <Metal/Metal.h>
8
9#include "flutter/fml/platform/darwin/scoped_nsobject.h"
10#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h"
11#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
12#import "flutter/shell/platform/darwin/ios/ios_surface.h"
13
15
17- (BOOL)flt_hasFirstResponderInViewHierarchySubtree {
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.
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.
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
78namespace flutter {
79// Becomes NO if Apple's API changes and blurred backdrop filters cannot be applied.
81
82std::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;
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
150void FlutterPlatformViewLayerPool::RecycleLayers() {
151 available_layer_index_ = 0;
152}
153
154std::vector<std::shared_ptr<FlutterPlatformViewLayer>>
155FlutterPlatformViewLayerPool::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
163void FlutterPlatformViewsController::SetFlutterView(UIView* flutter_view) {
164 flutter_view_.reset(flutter_view);
165}
166
167void FlutterPlatformViewsController::SetFlutterViewController(
168 UIViewController<FlutterViewResponder>* flutter_view_controller) {
169 flutter_view_controller_.reset(flutter_view_controller);
170}
171
172UIViewController<FlutterViewResponder>* FlutterPlatformViewsController::getFlutterViewController() {
173 return flutter_view_controller_.get();
174}
175
176void 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
190void 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) {
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
239 initWithEmbeddedView:platform_view
240 platformViewsController:GetWeakPtr()
241 gestureRecognizersBlockingPolicy:gesture_recognizers_blocking_policies_[viewType]];
242
243 touch_interceptors_[viewId] =
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
253void 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
268void FlutterPlatformViewsController::OnAcceptGesture(FlutterMethodCall* call,
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
286void FlutterPlatformViewsController::OnRejectGesture(FlutterMethodCall* call,
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
304void 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
314void FlutterPlatformViewsController::BeginFrame(SkISize frame_size) {
315 ResetFrameState();
316 frame_size_ = frame_size;
317}
318
319void 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`.
326bool FlutterPlatformViewsController::HasPlatformViewThisOrNextFrame() {
327 return !composition_order_.empty() || !active_composition_order_.empty();
328}
329
330const int FlutterPlatformViewsController::kDefaultMergedLeaseDuration;
331
332PostPrerollResult 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()) {
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);
358}
359
360void 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
368void 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
378void 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
401size_t FlutterPlatformViewsController::EmbeddedViewCount() {
402 return composition_order_.size();
403}
404
405UIView* FlutterPlatformViewsController::GetPlatformViewByID(int64_t view_id) {
407}
408
409FlutterTouchInterceptingView* FlutterPlatformViewsController::GetFlutterTouchInterceptingViewByID(
410 int64_t view_id) {
411 if (views_.empty()) {
412 return nil;
413 }
414 return touch_interceptors_[view_id].get();
415}
416
417long 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
426int 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
438void 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.
451void 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
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_`.
579void 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
618DlCanvas* 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
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
648SkRect 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
660bool 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
788void 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
823std::shared_ptr<FlutterPlatformViewLayer> FlutterPlatformViewsController::GetLayer(
824 GrDirectContext* gr_context,
825 const std::shared_ptr<IOSContext>& ios_context,
826 EmbedderViewSlice* slice,
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 layer->did_submit_last_frame = frame->Submit();
868 return layer;
869}
870
871void FlutterPlatformViewsController::RemoveUnusedLayers() {
872 std::vector<std::shared_ptr<FlutterPlatformViewLayer>> layers = layer_pool_->GetUnusedLayers();
873 for (const std::shared_ptr<FlutterPlatformViewLayer>& layer : layers) {
874 [layer->overlay_view_wrapper removeFromSuperview];
875 }
876
877 std::unordered_set<int64_t> composition_order_set;
878 for (int64_t view_id : composition_order_) {
879 composition_order_set.insert(view_id);
880 }
881 // Remove unused platform views.
882 for (int64_t view_id : active_composition_order_) {
883 if (composition_order_set.find(view_id) == composition_order_set.end()) {
884 UIView* platform_view_root = root_views_[view_id].get();
885 [platform_view_root removeFromSuperview];
886 }
887 }
888}
889
890void FlutterPlatformViewsController::DisposeViews() {
891 if (views_to_dispose_.empty()) {
892 return;
893 }
894
895 FML_DCHECK([[NSThread currentThread] isMainThread]);
896
897 std::unordered_set<int64_t> views_to_composite(composition_order_.begin(),
898 composition_order_.end());
899 std::unordered_set<int64_t> views_to_delay_dispose;
900 for (int64_t viewId : views_to_dispose_) {
901 if (views_to_composite.count(viewId)) {
902 views_to_delay_dispose.insert(viewId);
903 continue;
904 }
905 UIView* root_view = root_views_[viewId].get();
906 [root_view removeFromSuperview];
907 views_.erase(viewId);
908 touch_interceptors_.erase(viewId);
909 root_views_.erase(viewId);
910 current_composition_params_.erase(viewId);
911 clip_count_.erase(viewId);
912 views_to_recomposite_.erase(viewId);
913 }
914
915 views_to_dispose_ = std::move(views_to_delay_dispose);
916}
917
918void FlutterPlatformViewsController::BeginCATransaction() {
919 FML_DCHECK([[NSThread currentThread] isMainThread]);
920 FML_DCHECK(!catransaction_added_);
921 [CATransaction begin];
922 catransaction_added_ = true;
923}
924
925void FlutterPlatformViewsController::CommitCATransactionIfNeeded() {
926 if (catransaction_added_) {
927 FML_DCHECK([[NSThread currentThread] isMainThread]);
928 [CATransaction commit];
929 catransaction_added_ = false;
930 }
931}
932
933void FlutterPlatformViewsController::ResetFrameState() {
934 slices_.clear();
935 composition_order_.clear();
936 visited_platform_views_.clear();
937}
938
939} // namespace flutter
940
941// This recognizers delays touch events from being dispatched to the responder chain until it failed
942// recognizing a gesture.
943//
944// We only fail this recognizer when asked to do so by the Flutter framework (which does so by
945// invoking an acceptGesture method on the platform_views channel). And this is how we allow the
946// Flutter framework to delay or prevent the embedded view from getting a touch sequence.
947@interface DelayingGestureRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>
948
949// Indicates that if the `DelayingGestureRecognizer`'s state should be set to
950// `UIGestureRecognizerStateEnded` during next `touchesEnded` call.
952
953// Indicates that the `DelayingGestureRecognizer`'s `touchesEnded` has been invoked without
954// setting the state to `UIGestureRecognizerStateEnded`.
956
957@property(nonatomic, readonly) UIGestureRecognizer* forwardingRecognizer;
958
959- (instancetype)initWithTarget:(id)target
960 action:(SEL)action
961 forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer;
962@end
963
964// While the DelayingGestureRecognizer is preventing touches from hitting the responder chain
965// the touch events are not arriving to the FlutterView (and thus not arriving to the Flutter
966// framework). We use this gesture recognizer to dispatch the events directly to the FlutterView
967// while during this phase.
968//
969// If the Flutter framework decides to dispatch events to the embedded view, we fail the
970// DelayingGestureRecognizer which sends the events up the responder chain. But since the events
971// are handled by the embedded view they are not delivered to the Flutter framework in this phase
972// as well. So during this phase as well the ForwardingGestureRecognizer dispatched the events
973// directly to the FlutterView.
974@interface ForwardingGestureRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>
975- (instancetype)initWithTarget:(id)target
976 platformViewsController:
977 (fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController;
978@end
979
981@property(nonatomic, weak, readonly) UIView* embeddedView;
982@property(nonatomic, readonly) DelayingGestureRecognizer* delayingRecognizer;
984@end
985
987- (instancetype)initWithEmbeddedView:(UIView*)embeddedView
988 platformViewsController:
989 (fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController
990 gestureRecognizersBlockingPolicy:
992 self = [super initWithFrame:embeddedView.frame];
993 if (self) {
994 self.multipleTouchEnabled = YES;
995 _embeddedView = embeddedView;
996 embeddedView.autoresizingMask =
997 (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
998
999 [self addSubview:embeddedView];
1000
1002 [[ForwardingGestureRecognizer alloc] initWithTarget:self
1003 platformViewsController:platformViewsController];
1004
1005 _delayingRecognizer = [[DelayingGestureRecognizer alloc] initWithTarget:self
1006 action:nil
1007 forwardingRecognizer:forwardingRecognizer];
1008 _blockingPolicy = blockingPolicy;
1009
1010 [self addGestureRecognizer:_delayingRecognizer];
1011 [self addGestureRecognizer:forwardingRecognizer];
1012 }
1013 return self;
1014}
1015
1016- (void)releaseGesture {
1017 self.delayingRecognizer.state = UIGestureRecognizerStateFailed;
1018}
1019
1020- (void)blockGesture {
1021 switch (_blockingPolicy) {
1023 // We block all other gesture recognizers immediately in this policy.
1024 self.delayingRecognizer.state = UIGestureRecognizerStateEnded;
1025 break;
1027 if (self.delayingRecognizer.touchedEndedWithoutBlocking) {
1028 // If touchesEnded of the `DelayingGesureRecognizer` has been already invoked,
1029 // we want to set the state of the `DelayingGesureRecognizer` to
1030 // `UIGestureRecognizerStateEnded` as soon as possible.
1031 self.delayingRecognizer.state = UIGestureRecognizerStateEnded;
1032 } else {
1033 // If touchesEnded of the `DelayingGesureRecognizer` has not been invoked,
1034 // We will set a flag to notify the `DelayingGesureRecognizer` to set the state to
1035 // `UIGestureRecognizerStateEnded` when touchesEnded is called.
1036 self.delayingRecognizer.shouldEndInNextTouchesEnded = YES;
1037 }
1038 break;
1039 default:
1040 break;
1041 }
1042}
1043
1044// We want the intercepting view to consume the touches and not pass the touches up to the parent
1045// view. Make the touch event method not call super will not pass the touches up to the parent view.
1046// Hence we overide the touch event methods and do nothing.
1047- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
1048}
1049
1050- (void)touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
1051}
1052
1053- (void)touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
1054}
1055
1056- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1057}
1058
1060 return self.flutterAccessibilityContainer;
1061}
1062
1063@end
1064
1065@implementation DelayingGestureRecognizer
1066
1067- (instancetype)initWithTarget:(id)target
1068 action:(SEL)action
1069 forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer {
1070 self = [super initWithTarget:target action:action];
1071 if (self) {
1072 self.delaysTouchesBegan = YES;
1073 self.delaysTouchesEnded = YES;
1074 self.delegate = self;
1075 _shouldEndInNextTouchesEnded = NO;
1076 _touchedEndedWithoutBlocking = NO;
1077 _forwardingRecognizer = forwardingRecognizer;
1078 }
1079 return self;
1080}
1081
1082- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
1083 shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
1084 // The forwarding gesture recognizer should always get all touch events, so it should not be
1085 // required to fail by any other gesture recognizer.
1086 return otherGestureRecognizer != _forwardingRecognizer && otherGestureRecognizer != self;
1087}
1088
1089- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
1090 shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
1091 return otherGestureRecognizer == self;
1092}
1093
1094- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
1095 self.touchedEndedWithoutBlocking = NO;
1096 [super touchesBegan:touches withEvent:event];
1097}
1098
1099- (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
1100 if (self.shouldEndInNextTouchesEnded) {
1101 self.state = UIGestureRecognizerStateEnded;
1102 self.shouldEndInNextTouchesEnded = NO;
1103 } else {
1104 self.touchedEndedWithoutBlocking = YES;
1105 }
1106 [super touchesEnded:touches withEvent:event];
1107}
1108
1109- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1110 self.state = UIGestureRecognizerStateFailed;
1111}
1112@end
1113
1114@implementation ForwardingGestureRecognizer {
1115 // Weak reference to FlutterPlatformViewsController. The FlutterPlatformViewsController has
1116 // a reference to the FlutterViewController, where we can dispatch pointer events to.
1117 //
1118 // The lifecycle of FlutterPlatformViewsController is bind to FlutterEngine, which should always
1119 // outlives the FlutterViewController. And ForwardingGestureRecognizer is owned by a subview of
1120 // FlutterView, so the ForwardingGestureRecognizer never out lives FlutterViewController.
1121 // Therefore, `_platformViewsController` should never be nullptr.
1123 // Counting the pointers that has started in one touch sequence.
1125 // We can't dispatch events to the framework without this back pointer.
1126 // This gesture recognizer retains the `FlutterViewController` until the
1127 // end of a gesture sequence, that is all the touches in touchesBegan are concluded
1128 // with |touchesCancelled| or |touchesEnded|.
1130}
1131
1132- (instancetype)initWithTarget:(id)target
1133 platformViewsController:
1134 (fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController {
1135 self = [super initWithTarget:target action:nil];
1136 if (self) {
1137 self.delegate = self;
1138 FML_DCHECK(platformViewsController.get() != nullptr);
1139 _platformViewsController = std::move(platformViewsController);
1141 }
1142 return self;
1143}
1144
1145- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1147 if (_currentTouchPointersCount == 0) {
1148 // At the start of each gesture sequence, we reset the `_flutterViewController`,
1149 // so that all the touch events in the same sequence are forwarded to the same
1150 // `_flutterViewController`.
1151 _flutterViewController.reset(_platformViewsController->getFlutterViewController());
1152 }
1153 [_flutterViewController.get() touchesBegan:touches withEvent:event];
1155}
1156
1157- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1158 [_flutterViewController.get() touchesMoved:touches withEvent:event];
1159}
1160
1161- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1162 [_flutterViewController.get() touchesEnded:touches withEvent:event];
1164 // Touches in one touch sequence are sent to the touchesEnded method separately if different
1165 // fingers stop touching the screen at different time. So one touchesEnded method triggering does
1166 // not necessarially mean the touch sequence has ended. We Only set the state to
1167 // UIGestureRecognizerStateFailed when all the touches in the current touch sequence is ended.
1168 if (_currentTouchPointersCount == 0) {
1169 self.state = UIGestureRecognizerStateFailed;
1170 _flutterViewController.reset(nil);
1171 }
1172}
1173
1174- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1175 // In the event of platform view is removed, iOS generates a "stationary" change type instead of
1176 // "cancelled" change type.
1177 // Flutter needs all the cancelled touches to be "cancelled" change types in order to correctly
1178 // handle gesture sequence.
1179 // We always override the change type to "cancelled".
1180 [_flutterViewController.get() forceTouchesCancelled:touches];
1182 if (_currentTouchPointersCount == 0) {
1183 self.state = UIGestureRecognizerStateFailed;
1184 _flutterViewController.reset(nil);
1185 }
1186}
1187
1188- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
1189 shouldRecognizeSimultaneouslyWithGestureRecognizer:
1190 (UIGestureRecognizer*)otherGestureRecognizer {
1191 return YES;
1192}
1193@end
void(^ FlutterResult)(id _Nullable result)
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
std::unique_ptr< flutter::PlatformViewIOS > platform_view
fml::scoped_nsobject< UIViewController< FlutterViewResponder > > _flutterViewController
static bool ClipRectContainsPlatformViewBoundingRect(const SkRect &clip_rect, const SkRect &platformview_boundingrect, const SkMatrix &transform_matrix)
NSInteger _currentTouchPointersCount
static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect &clip_rrect, const SkRect &platformview_boundingrect, const SkMatrix &transform_matrix)
FlutterPlatformViewGestureRecognizersBlockingPolicy
@ FlutterPlatformViewGestureRecognizersBlockingPolicyEager
@ FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded
int count
Definition: FontMgrTest.cpp:50
static sk_sp< Effect > Create()
Definition: RefCntTest.cpp:117
DelayingGestureRecognizer * delayingRecognizer
FlutterPlatformViewGestureRecognizersBlockingPolicy blockingPolicy
SkScalar mapRadius(SkScalar radius) const
Definition: SkMatrix.cpp:1170
bool mapRect(SkRect *dst, const SkRect &src, SkApplyPerspectiveClip pc=SkApplyPerspectiveClip::kYes) const
Definition: SkMatrix.cpp:1141
const SkRect & rect() const
Definition: SkRRect.h:264
SkVector radii(Corner corner) const
Definition: SkRRect.h:271
void setRectRadii(const SkRect &rect, const SkVector radii[4])
Definition: SkRRect.cpp:189
bool contains(const SkRect &rect) const
Definition: SkRRect.cpp:360
Developer-facing API for rendering anything within the engine.
Definition: dl_canvas.h:38
virtual void ClipRect(const SkRect &rect, ClipOp clip_op=ClipOp::kIntersect, bool is_aa=false)=0
virtual void Flush()=0
std::vector< SkIRect > getRects(bool deband=true) const
Definition: dl_region.cc:563
virtual void render_into(DlCanvas *canvas)=0
virtual void end_recording()=0
DlRegion region(const SkRect &query) const
FlutterTouchInterceptingView * GetFlutterTouchInterceptingViewByID(int64_t view_id)
virtual std::unique_ptr< Surface > CreateGPUSurface(GrDirectContext *gr_context=nullptr)=0
const std::vector< std::shared_ptr< Mutator > >::const_reverse_iterator Top() const
const std::vector< std::shared_ptr< Mutator > >::const_reverse_iterator Bottom() const
void ExtendLeaseTo(size_t lease_term)
void MergeWithLease(size_t lease_term)
void reset(NST *object=Traits::InvalidValue(), scoped_policy::OwnershipPolicy policy=scoped_policy::OwnershipPolicy::kAssume)
@ kSuccess
Definition: embedder.h:73
const EmbeddedViewParams * params
VkSurfaceKHR surface
Definition: main.cc:49
double frame
Definition: examples.cpp:31
float SkScalar
Definition: extension.cpp:12
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
GAsyncResult * result
#define FML_CHECK(condition)
Definition: logging.h:85
#define FML_DCHECK(condition)
Definition: logging.h:103
void applyBlurBackdropFilters:(NSArray< PlatformViewFilter * > *filters)
UIGestureRecognizer * forwardingRecognizer
std::shared_ptr< flutter::FlutterPlatformViewsController > _platformViewsController
Win32Message message
constexpr SkColor4f kTransparent
Definition: SkColor.h:434
static bool init()
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350
def call(args)
Definition: dom.py:159
auto WeakPtr(std::shared_ptr< T > pointer)
BOOL canApplyBlurBackdrop
void ResetAnchor(CALayer *layer)
@ kBackdropFilter
CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix &matrix)
CGRect GetCGRectFromSkRect(const SkRect &clipSkRect)
void Reset(SkPath *path)
Definition: path_ops.cc:40
Definition: ascii_trie.cc:9
flutter::DlCanvas DlCanvas
Definition: SkRect.h:32
static bool Intersects(const SkIRect &a, const SkIRect &b)
Definition: SkRect.h:535
void join(const SkIRect &r)
Definition: SkRect.cpp:31
static constexpr SkIRect MakeEmpty()
Definition: SkRect.h:45
Definition: SkSize.h:16
constexpr float y() const
Definition: SkPoint_impl.h:187
constexpr float x() const
Definition: SkPoint_impl.h:181
static SkRect Make(const SkISize &size)
Definition: SkRect.h:669
void roundIn(SkIRect *dst) const
Definition: SkRect.h:1266
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition: SkRect.h:659
bool contains(SkScalar x, SkScalar y) const
Definition: extension.cpp:19
void roundOut(SkIRect *dst) const
Definition: SkRect.h:1241
const uintptr_t id
#define TRACE_EVENT0(category_group, name)
Definition: trace_event.h:131
int BOOL
Definition: windows_types.h:37