Skip to content

Commit

Permalink
[ios]enable the webview non tappable workaround by checking subviews …
Browse files Browse the repository at this point in the history
…recursively (#57168)

The original workaround ([PR](#56804)) works for the official web view plugin, but it doesn't work for a third party plugin `flutter_inappwebview` ([issue](https://github.com/pichillilorenzo/flutter_inappwebview)). Upon discussion with the author of that plugin, it turns out that their platform view is not a WKWebView, but rather a wrapper of WKWebView. 

This PR performs a DFS search of the view hierarchy, and enable the workaround as long as there's a WKWebView inside. 

TODO: pending sample project:
I am quite positive that it should work, but **I haven't tried it since I don't have a sample project yet**. I have requested a sample project with them so I can verify the solution. 

*List which issues are fixed by this PR. You must list at least one issue.*

 pichillilorenzo/flutter_inappwebview#2415

*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
  • Loading branch information
hellohuanlin authored Dec 13, 2024
1 parent 93b7c61 commit 4402232
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,18 @@ - (void)releaseGesture {
self.delayingRecognizer.state = UIGestureRecognizerStateFailed;
}

- (BOOL)containsWebView:(UIView*)view {
if ([view isKindOfClass:[WKWebView class]]) {
return YES;
}
for (UIView* subview in view.subviews) {
if ([self containsWebView:subview]) {
return YES;
}
}
return NO;
}

- (void)blockGesture {
switch (_blockingPolicy) {
case FlutterPlatformViewGestureRecognizersBlockingPolicyEager:
Expand All @@ -581,7 +593,7 @@ - (void)blockGesture {
// FlutterPlatformViewGestureRecognizersBlockingPolicyEager, but we should try it if a similar
// issue arises for the other policy.
if (@available(iOS 18.2, *)) {
if ([self.embeddedView isKindOfClass:[WKWebView class]]) {
if ([self containsWebView:self.embeddedView]) {
[self removeGestureRecognizer:self.delayingRecognizer];
[self addGestureRecognizer:self.delayingRecognizer];
}
Expand Down
105 changes: 105 additions & 0 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,46 @@ @implementation FlutterPlatformViewsTestNilFlutterPlatformFactory

@end

@interface FlutterPlatformViewsTestMockWrapperWebView : NSObject <FlutterPlatformView>
@property(nonatomic, strong) UIView* view;
@property(nonatomic, assign) BOOL viewCreated;
@end

@implementation FlutterPlatformViewsTestMockWrapperWebView
- (instancetype)init {
if (self = [super init]) {
_view = [[UIView alloc] init];
[_view addSubview:[[WKWebView alloc] init]];
gMockPlatformView = _view;
_viewCreated = NO;
}
return self;
}

- (UIView*)view {
[self checkViewCreatedOnce];
return _view;
}

- (void)checkViewCreatedOnce {
if (self.viewCreated) {
abort();
}
self.viewCreated = YES;
}
@end

@interface FlutterPlatformViewsTestMockWrapperWebViewFactory : NSObject <FlutterPlatformViewFactory>
@end

@implementation FlutterPlatformViewsTestMockWrapperWebViewFactory
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args {
return [[FlutterPlatformViewsTestMockWrapperWebView alloc] init];
}
@end

namespace flutter {
namespace {
class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::Delegate {
Expand Down Expand Up @@ -3153,6 +3193,71 @@ - (void)testFlutterPlatformViewTouchesEndedOrTouchesCancelledEventDoesNotFailThe
}
}

- (void)
testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldRemoveAndAddBackDelayingRecognizerForWrapperWebView {
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;

flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
/*platform=*/GetDefaultTaskRunner(),
/*raster=*/GetDefaultTaskRunner(),
/*ui=*/GetDefaultTaskRunner(),
/*io=*/GetDefaultTaskRunner());
FlutterPlatformViewsController* flutterPlatformViewsController =
[[FlutterPlatformViewsController alloc] init];
flutterPlatformViewsController.taskRunner = GetDefaultTaskRunner();
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
/*delegate=*/mock_delegate,
/*rendering_api=*/mock_delegate.settings_.enable_impeller
? flutter::IOSRenderingAPI::kMetal
: flutter::IOSRenderingAPI::kSoftware,
/*platform_views_controller=*/flutterPlatformViewsController,
/*task_runners=*/runners,
/*worker_task_runner=*/nil,
/*is_gpu_disabled_jsync_switch=*/std::make_shared<fml::SyncSwitch>());

FlutterPlatformViewsTestMockWrapperWebViewFactory* factory =
[[FlutterPlatformViewsTestMockWrapperWebViewFactory alloc] init];
[flutterPlatformViewsController
registerViewFactory:factory
withId:@"MockWrapperWebView"
gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
FlutterResult result = ^(id result) {
};
[flutterPlatformViewsController
onMethodCall:[FlutterMethodCall
methodCallWithMethodName:@"create"
arguments:@{@"id" : @2, @"viewType" : @"MockWrapperWebView"}]
result:result];

XCTAssertNotNil(gMockPlatformView);

// Find touch inteceptor view
UIView* touchInteceptorView = gMockPlatformView;
while (touchInteceptorView != nil &&
![touchInteceptorView isKindOfClass:[FlutterTouchInterceptingView class]]) {
touchInteceptorView = touchInteceptorView.superview;
}
XCTAssertNotNil(touchInteceptorView);

XCTAssert(touchInteceptorView.gestureRecognizers.count == 2);
UIGestureRecognizer* delayingRecognizer = touchInteceptorView.gestureRecognizers[0];
UIGestureRecognizer* forwardingRecognizer = touchInteceptorView.gestureRecognizers[1];

XCTAssert([delayingRecognizer isKindOfClass:[FlutterDelayingGestureRecognizer class]]);
XCTAssert([forwardingRecognizer isKindOfClass:[ForwardingGestureRecognizer class]]);

[(FlutterTouchInterceptingView*)touchInteceptorView blockGesture];

if (@available(iOS 18.2, *)) {
// Since we remove and add back delayingRecognizer, it would be reordered to the last.
XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], forwardingRecognizer);
XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], delayingRecognizer);
} else {
XCTAssertEqual(touchInteceptorView.gestureRecognizers[0], delayingRecognizer);
XCTAssertEqual(touchInteceptorView.gestureRecognizers[1], forwardingRecognizer);
}
}

- (void)
testFlutterPlatformViewBlockGestureUnderEagerPolicyShouldNotRemoveAndAddBackDelayingRecognizerForNonWebView {
flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
Expand Down

0 comments on commit 4402232

Please sign in to comment.