Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
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
43 changes: 43 additions & 0 deletions shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,32 @@ typedef void (*FlutterPluginRegistrantCallback)(NSObject<FlutterPluginRegistry>*
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
@end

#pragma mark -
/***************************************************************************************************
* How the UIGestureRecognizers of a platform view are blocked.
*
* UIGestureRecognizers of platform views can be blocked based on decisions made by the
* Flutter Framework (e.g. When an interact-able widget is covering the platform view).
*/
typedef enum {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typos: framework, policy, implemented,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

/**
* Flutter blocks all the UIGestureRecognizers on the platform view as soon as it
* decides they should be blocked.
*
* With this policy, only the `touchesBegan` method for all the UIGestureRecognizers is guaranteed
* to be called.
*/
FlutterPlatformViewGestureRecognizersBlockingPolicyEager,
/**
* Flutter blocks the platform view's UIGestureRecognizers from recognizing only after
* touchesEnded was invoked.
*
* This results in the platform view's UIGestureRecognizers seeing the entire touch sequence,
* but never recognizing the gesture (and never invoking actions).
*/
FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded,
} FlutterPlatformViewGestureRecognizersBlockingPolicy;

#pragma mark -
/***************************************************************************************************
*Registration context for a single `FlutterPlugin`, providing a one stop shop
Expand Down Expand Up @@ -264,6 +290,23 @@ typedef void (*FlutterPluginRegistrantCallback)(NSObject<FlutterPluginRegistry>*
- (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
withId:(NSString*)factoryId;

/**
* Registers a `FlutterPlatformViewFactory` for creation of platform views.
*
* Plugins can expose a `UIView` for embedding in Flutter apps by registering a view factory.
*
* @param factory The view factory that will be registered.
* @param factoryId A unique identifier for the factory, the Dart code of the Flutter app can use
* this identifier to request creation of a `UIView` by the registered factory.
* @param gestureBlockingPolicy How UIGestureRecognizers on the platform views are
* blocked.
*
*/
- (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
withId:(NSString*)factoryId
gestureRecognizersBlockingPolicy:
(FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizersBlockingPolicy;

/**
* Publishes a value for external use of the plugin.
*
Expand Down
12 changes: 11 additions & 1 deletion shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,17 @@ - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {

- (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
withId:(NSString*)factoryId {
[_flutterEngine platformViewsController] -> RegisterViewFactory(factory, factoryId);
[self registerViewFactory:factory
withId:factoryId
gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
}

- (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
withId:(NSString*)factoryId
gestureRecognizersBlockingPolicy:
(FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizersBlockingPolicy {
[_flutterEngine platformViewsController] -> RegisterViewFactory(factory, factoryId,
gestureRecognizersBlockingPolicy);
}

@end
65 changes: 60 additions & 5 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,10 @@
views_[viewId] = fml::scoped_nsobject<NSObject<FlutterPlatformView>>([embedded_view retain]);

FlutterTouchInterceptingView* touch_interceptor = [[[FlutterTouchInterceptingView alloc]
initWithEmbeddedView:embedded_view.view
flutterViewController:flutter_view_controller_.get()] autorelease];
initWithEmbeddedView:embedded_view.view
flutterViewController:flutter_view_controller_.get()
gestureRecognizersBlockingPolicy:gesture_recognizers_blocking_policies[viewType]]
autorelease];

touch_interceptors_[viewId] =
fml::scoped_nsobject<FlutterTouchInterceptingView>([touch_interceptor retain]);
Expand Down Expand Up @@ -149,11 +151,13 @@

void FlutterPlatformViewsController::RegisterViewFactory(
NSObject<FlutterPlatformViewFactory>* factory,
NSString* factoryId) {
NSString* factoryId,
FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy) {
std::string idString([factoryId UTF8String]);
FML_CHECK(factories_.count(idString) == 0);
factories_[idString] =
fml::scoped_nsobject<NSObject<FlutterPlatformViewFactory>>([factory retain]);
gesture_recognizers_blocking_policies[idString] = gestureRecognizerBlockingPolicy;
}

void FlutterPlatformViewsController::SetFrameSize(SkISize frame_size) {
Expand Down Expand Up @@ -513,6 +517,15 @@
// invoking an acceptGesture method on the platform_views channel). And this is how we allow the
// Flutter framework to delay or prevent the embedded view from getting a touch sequence.
@interface DelayingGestureRecognizer : UIGestureRecognizer <UIGestureRecognizerDelegate>

// Indicates that if the `DelayingGestureRecognizer`'s state should be set to
// `UIGestureRecognizerStateEnded` during next `touchesEnded` call.
@property(nonatomic) bool shouldEndInNextTouchesEnded;

// Indicates that the `DelayingGestureRecognizer`'s `touchesEnded` has been invoked without
// setting the state to `UIGestureRecognizerStateEnded`.
@property(nonatomic) bool touchedEndedWithoutBlocking;

- (instancetype)initWithTarget:(id)target
action:(SEL)action
forwardingRecognizer:(UIGestureRecognizer*)forwardingRecognizer;
Expand All @@ -535,9 +548,12 @@ - (instancetype)initWithTarget:(id)target

@implementation FlutterTouchInterceptingView {
fml::scoped_nsobject<DelayingGestureRecognizer> _delayingRecognizer;
FlutterPlatformViewGestureRecognizersBlockingPolicy _blockingPolicy;
}
- (instancetype)initWithEmbeddedView:(UIView*)embeddedView
flutterViewController:(UIViewController*)flutterViewController {
flutterViewController:(UIViewController*)flutterViewController
gestureRecognizersBlockingPolicy:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this the auto indentation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, indented by the formatter

(FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy {
self = [super initWithFrame:embeddedView.frame];
if (self) {
self.multipleTouchEnabled = YES;
Expand All @@ -554,6 +570,7 @@ - (instancetype)initWithEmbeddedView:(UIView*)embeddedView
initWithTarget:self
action:nil
forwardingRecognizer:forwardingRecognizer]);
_blockingPolicy = blockingPolicy;

[self addGestureRecognizer:_delayingRecognizer.get()];
[self addGestureRecognizer:forwardingRecognizer];
Expand All @@ -566,7 +583,27 @@ - (void)releaseGesture {
}

- (void)blockGesture {
_delayingRecognizer.get().state = UIGestureRecognizerStateEnded;
switch (_blockingPolicy) {
case FlutterPlatformViewGestureRecognizersBlockingPolicyEager:
// We block all other gesture recognizers immediately in this policy.
_delayingRecognizer.get().state = UIGestureRecognizerStateEnded;
break;
case FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded:
if (_delayingRecognizer.get().touchedEndedWithoutBlocking) {
// If touchesEnded of the `DelayingGesureRecognizer` has been already invoked,
// we want to set the state of the `DelayingGesureRecognizer` to
// `UIGestureRecognizerStateEnded` as soon as possible.
_delayingRecognizer.get().state = UIGestureRecognizerStateEnded;
} else {
// If touchesEnded of the `DelayingGesureRecognizer` has not been invoked,
// We will set a flag to notify the `DelayingGesureRecognizer` to set the state to
// `UIGestureRecognizerStateEnded` when touchesEnded is called.
_delayingRecognizer.get().shouldEndInNextTouchesEnded = YES;
}
break;
default:
break;
}
}

// We want the intercepting view to consume the touches and not pass the touches up to the parent
Expand Down Expand Up @@ -596,7 +633,10 @@ - (instancetype)initWithTarget:(id)target
self = [super initWithTarget:target action:action];
if (self) {
self.delaysTouchesBegan = YES;
self.delaysTouchesEnded = YES;
self.delegate = self;
self.shouldEndInNextTouchesEnded = NO;
self.touchedEndedWithoutBlocking = NO;
_forwardingRecognizer.reset([forwardingRecognizer retain]);
}
return self;
Expand All @@ -614,6 +654,21 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
return otherGestureRecognizer == self;
}

- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
self.touchedEndedWithoutBlocking = NO;
[super touchesBegan:touches withEvent:event];
}

- (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event {
if (self.shouldEndInNextTouchesEnded) {
self.state = UIGestureRecognizerStateEnded;
self.shouldEndInNextTouchesEnded = NO;
} else {
self.touchedEndedWithoutBlocking = YES;
}
[super touchesEnded:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
self.state = UIGestureRecognizerStateFailed;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h"
#include "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h"
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h"

// A UIView that is used as the parent for embedded UIViews.
//
Expand All @@ -19,7 +20,9 @@
// 2. Dispatching all events that are hittested to the embedded view to the FlutterView.
@interface FlutterTouchInterceptingView : UIView
- (instancetype)initWithEmbeddedView:(UIView*)embeddedView
flutterViewController:(UIViewController*)flutterViewController;
flutterViewController:(UIViewController*)flutterViewController
gestureRecognizersBlockingPolicy:
(FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy;

// Stop delaying any active touch sequence (and let it arrive the embedded view).
- (void)releaseGesture;
Expand Down Expand Up @@ -80,7 +83,10 @@ class FlutterPlatformViewsController {

void SetFlutterViewController(UIViewController* flutter_view_controller);

void RegisterViewFactory(NSObject<FlutterPlatformViewFactory>* factory, NSString* factoryId);
void RegisterViewFactory(
NSObject<FlutterPlatformViewFactory>* factory,
NSString* factoryId,
FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy);

void SetFrameSize(SkISize frame_size);

Expand Down Expand Up @@ -152,6 +158,10 @@ class FlutterPlatformViewsController {
// Only compoiste platform views in this set.
std::unordered_set<int64_t> views_to_recomposite_;

// The FlutterPlatformViewGestureRecognizersBlockingPolicy for each type of platform view.
std::map<std::string, FlutterPlatformViewGestureRecognizersBlockingPolicy>
gesture_recognizers_blocking_policies;

std::map<int64_t, std::unique_ptr<SkPictureRecorder>> picture_recorders_;

void OnCreate(FlutterMethodCall* call, FlutterResult& result);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.flutter.plugins;

import io.flutter.plugin.common.PluginRegistry;

/**
* Generated file. Do not edit.
*/
public final class GeneratedPluginRegistrant {
public static void registerWith(PluginRegistry registry) {
if (alreadyRegisteredWith(registry)) {
return;
}
}

private static boolean alreadyRegisteredWith(PluginRegistry registry) {
final String key = GeneratedPluginRegistrant.class.getCanonicalName();
if (registry.hasPlugin(key)) {
return true;
}
registry.registrarFor(key);
return false;
}
}
17 changes: 17 additions & 0 deletions testing/scenario_app/ios/Runner/GeneratedPluginRegistrant.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// Generated file. Do not edit.
//

#ifndef GeneratedPluginRegistrant_h
#define GeneratedPluginRegistrant_h

#import <Flutter/Flutter.h>

NS_ASSUME_NONNULL_BEGIN

@interface GeneratedPluginRegistrant : NSObject
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
@end

NS_ASSUME_NONNULL_END
#endif /* GeneratedPluginRegistrant_h */
12 changes: 12 additions & 0 deletions testing/scenario_app/ios/Runner/GeneratedPluginRegistrant.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// Generated file. Do not edit.
//

#import "GeneratedPluginRegistrant.h"

@implementation GeneratedPluginRegistrant

+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
6816DBAC2318696600A51400 /* golden_platform_view_opacity_iPhone SE_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6816DBA72318696600A51400 /* golden_platform_view_opacity_iPhone SE_simulator.png */; };
6816DBAD2318696600A51400 /* golden_platform_view_cliprect_iPhone SE_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6816DBA82318696600A51400 /* golden_platform_view_cliprect_iPhone SE_simulator.png */; };
6816DBAE2318696600A51400 /* golden_platform_view_cliprrect_iPhone SE_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 6816DBA92318696600A51400 /* golden_platform_view_cliprrect_iPhone SE_simulator.png */; };
68A5B63423EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 68A5B63323EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -156,6 +157,7 @@
6816DBA72318696600A51400 /* golden_platform_view_opacity_iPhone SE_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_opacity_iPhone SE_simulator.png"; sourceTree = "<group>"; };
6816DBA82318696600A51400 /* golden_platform_view_cliprect_iPhone SE_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_cliprect_iPhone SE_simulator.png"; sourceTree = "<group>"; };
6816DBA92318696600A51400 /* golden_platform_view_cliprrect_iPhone SE_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_cliprrect_iPhone SE_simulator.png"; sourceTree = "<group>"; };
68A5B63323EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlatformViewGestureRecognizerTests.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -269,6 +271,7 @@
6816DBA02317573300A51400 /* GoldenImage.m */,
6816DBA22318358200A51400 /* PlatformViewGoldenTestManager.h */,
6816DBA32318358200A51400 /* PlatformViewGoldenTestManager.m */,
68A5B63323EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m */,
);
path = ScenariosUITests;
sourceTree = "<group>";
Expand Down Expand Up @@ -458,6 +461,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
68A5B63423EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m in Sources */,
6816DBA12317573300A51400 /* GoldenImage.m in Sources */,
6816DB9E231750ED00A51400 /* GoldenPlatformViewTests.m in Sources */,
6816DBA42318358200A51400 /* PlatformViewGoldenTestManager.m in Sources */,
Expand Down
22 changes: 16 additions & 6 deletions testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ - (BOOL)application:(UIApplication*)application

// This argument is used by the XCUITest for Platform Views so that the app
// under test will create platform views.
// The launchArgsMap should match the one in the `PlatformVieGoldenTestManager`.
// If the test is one of the platform view golden tests,
// the launchArgsMap should match the one in the `PlatformVieGoldenTestManager`.
NSDictionary<NSString*, NSString*>* launchArgsMap = @{
@"--platform-view" : @"platform_view",
@"--platform-view-multiple" : @"platform_view_multiple",
Expand All @@ -37,18 +38,21 @@ - (BOOL)application:(UIApplication*)application
@"--platform-view-transform" : @"platform_view_transform",
@"--platform-view-opacity" : @"platform_view_opacity",
@"--platform-view-rotate" : @"platform_view_rotate",
@"--gesture-reject-after-touches-ended" : @"platform_view_gesture_reject_after_touches_ended",
@"--gesture-reject-eager" : @"platform_view_gesture_reject_eager",
@"--gesture-accept" : @"platform_view_gesture_accept",
};
__block NSString* goldenTestName = nil;
__block NSString* platformViewTestName = nil;
[launchArgsMap
enumerateKeysAndObjectsUsingBlock:^(NSString* argument, NSString* testName, BOOL* stop) {
if ([[[NSProcessInfo processInfo] arguments] containsObject:argument]) {
goldenTestName = testName;
platformViewTestName = testName;
*stop = YES;
}
}];

if (goldenTestName) {
[self readyContextForPlatformViewTests:goldenTestName];
if (platformViewTestName) {
[self readyContextForPlatformViewTests:platformViewTestName];
} else if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--screen-before-flutter"]) {
self.window.rootViewController = [[ScreenBeforeFlutter alloc] initWithEngineRunCompletion:nil];
} else {
Expand Down Expand Up @@ -76,7 +80,13 @@ - (void)readyContextForPlatformViewTests:(NSString*)scenarioIdentifier {
[[TextPlatformViewFactory alloc] initWithMessenger:flutterViewController.binaryMessenger];
NSObject<FlutterPluginRegistrar>* registrar =
[flutterViewController.engine registrarForPlugin:@"scenarios/TextPlatformViewPlugin"];
[registrar registerViewFactory:textPlatformViewFactory withId:@"scenarios/textPlatformView"];
[registrar registerViewFactory:textPlatformViewFactory
withId:@"scenarios/textPlatformView"
gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
[registrar registerViewFactory:textPlatformViewFactory
withId:@"scenarios/textPlatformView_blockPolicyUntilTouchesEnded"
gestureRecognizersBlockingPolicy:
FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded];
self.window.rootViewController = flutterViewController;
}

Expand Down
Loading