| Flutter iOS Embedder |

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