Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h"
#import "flutter/shell/platform/darwin/ios/platform_message_handler_ios.h"
#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
#import "flutter/shell/platform/darwin/ios/rendering_api_selection.h"
#include "flutter/shell/profiling/sampling_profiler.h"
Expand Down Expand Up @@ -82,6 +83,31 @@ static void IOSPlatformThreadConfigSetter(const fml::Thread::ThreadConfig& confi
NSString* const kFlutterKeyDataChannel = @"flutter/keydata";
static constexpr int kNumProfilerSamplesPerSec = 5;

@interface PlatformViewChannelTaskQueue : NSObject <FlutterTaskQueue>

- (instancetype)initWithTaskRunner:(fml::RefPtr<fml::TaskRunner>)taskRunner;
- (void)dispatch:(dispatch_block_t)block;
@end

@implementation PlatformViewChannelTaskQueue {
fml::RefPtr<fml::TaskRunner> _taskRunner;
}

- (instancetype)initWithTaskRunner:(fml::RefPtr<fml::TaskRunner>)taskRunner {
self = [super init];
if (!self) {
return nil;
}
_taskRunner = taskRunner;
return self;
}

- (void)dispatch:(dispatch_block_t)block {
fml::TaskRunner::RunNowOrPostTask(_taskRunner,
[block_copy = Block_copy(block)]() { block_copy(); });
}
@end

@interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar>
@property(nonatomic, assign) FlutterEngine* flutterEngine;
- (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine;
Expand All @@ -100,6 +126,7 @@ @interface FlutterEngine () <FlutterIndirectScribbleDelegate,
@property(nonatomic, readwrite, copy) NSString* isolateId;
@property(nonatomic, copy) NSString* initialRoute;
@property(nonatomic, retain) id<NSObject> flutterViewControllerWillDeallocObserver;
@property(nonatomic, retain) PlatformViewChannelTaskQueue* platformViewTaskQueue;

#pragma mark - Embedder API properties

Expand Down Expand Up @@ -308,6 +335,7 @@ - (void)dealloc {
[_textureRegistry release];
_textureRegistry = nil;
[_isolateId release];
[_platformViewTaskQueue release];

NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
if (_flutterViewControllerWillDeallocObserver) {
Expand Down Expand Up @@ -620,7 +648,8 @@ - (void)setUpChannels {
_platformViewsChannel.reset([[FlutterMethodChannel alloc]
initWithName:@"flutter/platform_views"
binaryMessenger:self.binaryMessenger
codec:[FlutterStandardMethodCodec sharedInstance]]);
codec:[FlutterStandardMethodCodec sharedInstance]
taskQueue:_platformViewTaskQueue]);

_textInputChannel.reset([[FlutterMethodChannel alloc]
initWithName:@"flutter/textinput"
Expand Down Expand Up @@ -716,11 +745,12 @@ - (void)maybeSetupPlatformViewChannels {
[platformPlugin handleMethodCall:call result:result];
}];

fml::WeakPtr<FlutterEngine> weakSelf = [self getWeakPtr];
std::weak_ptr<flutter::FlutterPlatformViewsController> weakPlatformViewsController =
_platformViewsController;
[_platformViewsChannel.get()
setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if (weakSelf) {
weakSelf.get().platformViewsController->OnMethodCall(call, result);
if (!weakPlatformViewsController.expired()) {
weakPlatformViewsController.lock()->OnMethodCall(call, result);
}
}];

Expand Down
117 changes: 109 additions & 8 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,35 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
}

void FlutterPlatformViewsController::OnMethodCall(FlutterMethodCall* call, FlutterResult& result) {
// This method is invoked on UI thread.
// Generally, all the method channel calls need to be dispatched to the main thread.
// The exceptions being gesture handling.
BOOL hitTestBlocking = [[[NSBundle mainBundle]
objectForInfoDictionaryKey:@"FLTPlatformViewHitTestBlocking"] boolValue];
if ([[call method] isEqualToString:@"create"]) {
OnCreate(call, result);
dispatch_async(dispatch_get_main_queue(), ^{
OnCreate(call, result);
});
} else if ([[call method] isEqualToString:@"dispose"]) {
OnDispose(call, result);
dispatch_async(dispatch_get_main_queue(), ^{
OnDispose(call, result);
});
} else if ([[call method] isEqualToString:@"acceptGesture"]) {
OnAcceptGesture(call, result);
if (hitTestBlocking) {
OnHitTestResult(call, result, YES);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
OnAcceptGesture(call, result);
});
}
} else if ([[call method] isEqualToString:@"rejectGesture"]) {
OnRejectGesture(call, result);
if (hitTestBlocking) {
OnHitTestResult(call, result, NO);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
OnRejectGesture(call, result);
});
}
} else {
result(FlutterMethodNotImplemented);
}
Expand All @@ -198,6 +219,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
result([FlutterError errorWithCode:@"recreating_view"
message:@"trying to create an already created view"
details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
return;
}

NSObject<FlutterPlatformViewFactory>* factory = factories_[viewType].get();
Expand All @@ -207,7 +229,8 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
message:[NSString stringWithFormat:@"A UIKitView widget is trying to create a "
@"PlatformView with an unregistered type: < %@ >",
viewTypeString]
details:@"If you are the author of the PlatformView, make sure `registerViewFactory` "
details:@"If you are the author of the PlatformView, make sure "
@"`registerViewFactory` "
@"is invoked.\n"
@"See: "
@"https://docs.flutter.dev/development/platform-integration/"
Expand Down Expand Up @@ -303,6 +326,25 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
result(nil);
}

void FlutterPlatformViewsController::OnHitTestResult(FlutterMethodCall* call,
FlutterResult& result,
BOOL platformViewConsumesTouches) {
NSDictionary<NSString*, id>* args = [call arguments];
int64_t viewId = [args[@"id"] longLongValue];

if (views_.count(viewId) == 0) {
result([FlutterError errorWithCode:@"unknown_view"
message:@"trying to send hit test result for an unknown view"
details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
return;
}

FlutterTouchInterceptingView* view = touch_interceptors_[viewId].get();
[view respondToHitTestResult:platformViewConsumesTouches];

result(nil);
}

void FlutterPlatformViewsController::RegisterViewFactory(
NSObject<FlutterPlatformViewFactory>* factory,
NSString* factoryId,
Expand Down Expand Up @@ -378,6 +420,14 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
}
}

void FlutterPlatformViewsController::BlockPlatformThread() {
platform_thread_mutex_.lock();
}

void FlutterPlatformViewsController::ReleasePlatformThread() {
platform_thread_mutex_.unlock();
}

void FlutterPlatformViewsController::PrerollCompositeEmbeddedView(
int64_t view_id,
std::unique_ptr<EmbeddedViewParams> params) {
Expand Down Expand Up @@ -957,6 +1007,9 @@ @implementation FlutterTouchInterceptingView {
fml::scoped_nsobject<DelayingGestureRecognizer> _delayingRecognizer;
FlutterPlatformViewGestureRecognizersBlockingPolicy _blockingPolicy;
UIView* _embeddedView;
FlutterViewController* _flutterViewController;
BOOL _platformViewConsumesTouches;
flutter::FlutterPlatformViewsController* _platformViewsController;
}
- (instancetype)initWithEmbeddedView:(UIView*)embeddedView
platformViewsController:
Expand All @@ -965,12 +1018,16 @@ - (instancetype)initWithEmbeddedView:(UIView*)embeddedView
(FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy {
self = [super initWithFrame:embeddedView.frame];
if (self) {
_platformViewConsumesTouches = YES;
self.multipleTouchEnabled = YES;
_embeddedView = embeddedView;
embeddedView.autoresizingMask =
(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);

[self addSubview:embeddedView];
_platformViewsController = platformViewsController.get();
_flutterViewController =
((FlutterViewController*)_platformViewsController->getFlutterViewController());

ForwardingGestureRecognizer* forwardingRecognizer = [[[ForwardingGestureRecognizer alloc]
initWithTarget:self
Expand All @@ -981,9 +1038,14 @@ - (instancetype)initWithEmbeddedView:(UIView*)embeddedView
action:nil
forwardingRecognizer:forwardingRecognizer]);
_blockingPolicy = blockingPolicy;

[self addGestureRecognizer:_delayingRecognizer.get()];
[self addGestureRecognizer:forwardingRecognizer];
forwardingRecognizer.enabled = NO;
_delayingRecognizer.get().enabled = NO;
BOOL hitTestBlocking = [[[NSBundle mainBundle]
objectForInfoDictionaryKey:@"FLTPlatformViewHitTestBlocking"] boolValue];
if (!hitTestBlocking) {
[self addGestureRecognizer:_delayingRecognizer.get()];
[self addGestureRecognizer:forwardingRecognizer];
}
}
return self;
}
Expand All @@ -992,6 +1054,12 @@ - (UIView*)embeddedView {
return [[_embeddedView retain] autorelease];
}

- (void)respondToHitTestResult:(BOOL)platformViewConsumesTouches {
NSLog(@"respondToHitTestResult: %@", @(platformViewConsumesTouches));
_platformViewConsumesTouches = platformViewConsumesTouches;
_platformViewsController->ReleasePlatformThread();
}

- (void)releaseGesture {
_delayingRecognizer.get().state = UIGestureRecognizerStateFailed;
}
Expand Down Expand Up @@ -1020,6 +1088,39 @@ - (void)blockGesture {
}
}

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event {
if (event.type != UIEventTypeTouches) {
UIView* hittest = [super hitTest:point withEvent:event];
NSLog(@"not touch type, platform view takes the gesture, hittest: %@", hittest);
return hittest;
}
BOOL hasDownTouch = NO;
for (UITouch* touch in event.allTouches) {
NSLog(@"phase %@", @(touch.phase));
// Gesture arena for PlatformView hit tests only cares about the touch began location.
if (touch.phase == UITouchPhaseBegan) {
hasDownTouch = YES;
break;
}
}
if (event.allTouches.count > 0 && !hasDownTouch) {
UIView* hittest = [super hitTest:point withEvent:event];
NSLog(@"not a down touch, defaults to default hittest: %@", hittest);
return _platformViewConsumesTouches ? hittest : nil;
}
[_flutterViewController sendFakeTouchEventWithLocation:point
change:flutter::PointerData::Change::kDown];
[_flutterViewController sendFakeTouchEventWithLocation:point
change:flutter::PointerData::Change::kUp];
NSLog(@"send fake down touch, lock");
_platformViewsController->BlockPlatformThread();
UIView* hittest = [super hitTest:point withEvent:event];
if (_platformViewConsumesTouches) {
NSLog(@"unlocked platformview consumes gesture, hit test %@", hittest);
}
return _platformViewConsumesTouches ? hittest : nil;
}

// We want the intercepting view to consume the touches and not pass the touches up to the parent
// view. Make the touch event method not call super will not pass the touches up to the parent view.
// Hence we overide the touch event methods and do nothing.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,15 +266,22 @@ class FlutterPlatformViewsController {
// Pushes the view id of a visted platform view to the list of visied platform views.
void PushVisitedPlatformView(int64_t view_id) { visited_platform_views_.push_back(view_id); }

void BlockPlatformThread();
void ReleasePlatformThread();

private:
static const size_t kMaxLayerAllocations = 2;
std::mutex platform_thread_mutex_;

using LayersMap = std::map<int64_t, std::vector<std::shared_ptr<FlutterPlatformViewLayer>>>;

void OnCreate(FlutterMethodCall* call, FlutterResult& result);
void OnDispose(FlutterMethodCall* call, FlutterResult& result);
void OnAcceptGesture(FlutterMethodCall* call, FlutterResult& result);
void OnRejectGesture(FlutterMethodCall* call, FlutterResult& result);
void OnHitTestResult(FlutterMethodCall* call,
FlutterResult& result,
BOOL platformViewConsumesTouches);
// Dispose the views in `views_to_dispose_`.
void DisposeViews();

Expand Down Expand Up @@ -416,6 +423,8 @@ class FlutterPlatformViewsController {
gestureRecognizersBlockingPolicy:
(FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy;

- (void)respondToHitTestResult:(BOOL)platformViewConsumesTouches;

// Stop delaying any active touch sequence (and let it arrive the embedded view).
- (void)releaseGesture;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -517,18 +517,20 @@ - (void)loadView {
return pointer_data;
}

static void SendFakeTouchEvent(UIScreen* screen,
FlutterEngine* engine,
CGPoint location,
flutter::PointerData::Change change) {
- (void)sendFakeTouchEventWithLocation:(CGPoint)location
change:(flutter::PointerData::Change)change {
UIScreen* screen = [self flutterScreenIfViewLoaded];
if (!screen) {
return;
}
const CGFloat scale = screen.scale;
flutter::PointerData pointer_data = [[engine viewController] generatePointerDataForFake];
flutter::PointerData pointer_data = [[_engine.get() viewController] generatePointerDataForFake];
pointer_data.physical_x = location.x * scale;
pointer_data.physical_y = location.y * scale;
auto packet = std::make_unique<flutter::PointerDataPacket>(/*count=*/1);
pointer_data.change = change;
packet->SetPointerData(0, pointer_data);
[engine dispatchPointerDataPacket:std::move(packet)];
[_engine.get() dispatchPointerDataPacket:std::move(packet)];
}

- (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
Expand All @@ -538,8 +540,8 @@ - (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
CGPoint statusBarPoint = CGPointZero;
UIScreen* screen = [self flutterScreenIfViewLoaded];
if (screen) {
SendFakeTouchEvent(screen, _engine.get(), statusBarPoint, flutter::PointerData::Change::kDown);
SendFakeTouchEvent(screen, _engine.get(), statusBarPoint, flutter::PointerData::Change::kUp);
[self sendFakeTouchEventWithLocation:statusBarPoint change:flutter::PointerData::Change::kDown];
[self sendFakeTouchEventWithLocation:statusBarPoint change:flutter::PointerData::Change::kUp];
}
return NO;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERVIEWCONTROLLER_INTERNAL_H_

#include "flutter/fml/memory/weak_ptr.h"
#include "flutter/lib/ui/window/pointer_data.h"

#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterKeySecondaryResponder.h"
Expand Down Expand Up @@ -65,6 +66,8 @@ typedef void (^FlutterKeyboardAnimationCallback)(fml::TimePoint);
- (void)addInternalPlugins;
- (void)deregisterNotifications;
- (int32_t)accessibilityFlags;
- (void)sendFakeTouchEventWithLocation:(CGPoint)location
change:(flutter::PointerData::Change)change;

@end

Expand Down