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