Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS][WebView] Gesture recognizers not blocked in iOS 13.4 and 13.5 #53490

Closed
iKK001 opened this issue Mar 28, 2020 · 103 comments
Closed

[iOS][WebView] Gesture recognizers not blocked in iOS 13.4 and 13.5 #53490

iKK001 opened this issue Mar 28, 2020 · 103 comments
Assignees
Labels
a: platform-views Embedding Android/iOS views in Flutter apps c: regression It was better in the past than it is now customer: crowd Affects or could affect many people, though not necessarily a specific customer. customer: money (g3) e: OS-version specific Affects only some versions of the relevant operating system f: gestures flutter/packages/flutter/gestures repository. p: webview The WebView plugin P0 Critical issues such as a build break or regression package flutter/packages repository. See also p: labels. platform-ios iOS applications specifically

Comments

@iKK001
Copy link

iKK001 commented Mar 28, 2020

After having installed Mac OS Catalina 10.15.4, Xcode 11.4 and iOS13.4, my Flutter Application running on iOS (simulator and actual device [iPhoneX]) is very buggy !

Before, I had a floatingActionButton working fine. And also Drawer (left and right) where working fine.

Underneath the FloatingActionButton and Drawers is a WebView.

But after the Mac SW update:

  • floatingActionButton makes a screen-freeze
  • Drawer shows but freezes when trying to close it

I tried the following two configs:
1.) Flutter STABLE 1.12.13+hotfix.8, plus the Xcode-11.4 workaround described here...
2.) Flutter BETA 1.15.17

But, again, for both configs same error behaviour under iOS - screen-freezes without error-message.

On Android, everything works fine.

What is wrong under iOS?

Here excerpts of the pubspec.yaml :

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  webview_flutter: ^0.3.19+9
  cloud_firestore: ^0.13.4+1
  firebase_core: ^0.4.4+2
  firebase_auth: 0.15.3
  provider: ^4.0.4
  flutter_spinkit: ^4.1.2
  device_info: ^0.4.2+1
  page_transition: ^1.1.5
  package_info: ^0.4.0+16
  crypto: ^2.1.3
  shared_preferences: ^0.5.6+3
  url_launcher: ^5.4.2
  flutter_background_geolocation: ^1.7.1

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2

dependency_overrides:
  firebase_core: 0.4.4

dev_dependencies:
  flutter_test:
    sdk: flutter

Here is my flutter doctor -v :

[✓] Flutter (Channel beta, v1.15.17, on Mac OS X 10.15.4 19E266, locale en-CH)
    • Flutter version 1.15.17 at /Users/myName/Documents/flutter
    • Framework revision 2294d75bfa (3 weeks ago), 2020-03-07 00:28:38 +0900Engine revision 5aff311948
    • Dart version 2.8.0 (build 2.8.0-dev.12.0 9983424a3c)
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    • Android SDK at /Users/myName/Library/Android/sdk
    • Android NDK location not configured (optional; useful for native profiling support)
    • Platform android-29, build-tools 29.0.2Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 11.4)
    • Xcode at /Applications/Xcode.app/Contents/DeveloperXcode 11.4, Build version 11E146CocoaPods version 1.8.4

[✓] Android Studio (version 3.6)
    • Android Studio at /Applications/Android Studio.app/ContentsFlutter plugin version 44.0.2Dart plugin version 192.7761Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)

[✓] VS Code (version 1.43.2)
    • VS Code at /Applications/Visual Studio Code.app/ContentsFlutter extension version 3.8.1

[✓] Connected device (2 available)
    • Android SDK built for x86 • emulator-5554                            • android-x86 • Android 10 (API 29) (emulator)
    • MyPhone                     • d709a13432kjie7429258466dbas3f29r45gdl8s • ios         • iOS 13.4No issues found!

edited by cyanglaz
Internal: b/155322611

@iKK001 iKK001 changed the title After MacOS and Xcode update, Flutter App no longer works. After Mac SW update, Flutter App on iOSno longer works. Mar 28, 2020
@iKK001 iKK001 changed the title After Mac SW update, Flutter App on iOSno longer works. After Mac SW update, Flutter App on iOS no longer works. Mar 28, 2020
@VladyslavBondarenko
Copy link

Hi @iKK001
Can you post complete minimal code sample to reproduce your case?
With this simplest code I don't experience any freezes.
Tried with beta 1.15.17 and iPhone 7 iOS 13.4 after MacOS and Xcode update

code
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final appTitle = 'Drawer Demo';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: appTitle,
      home: MyHomePage(title: appTitle),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;

  MyHomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: WebView(
        initialUrl: 'https://flutter.dev/',
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          print('pressed');
        },
        child: Icon(Icons.navigation),
        backgroundColor: Colors.green,
      ),
      drawer: Drawer(
        child: ListView(
          padding: EdgeInsets.zero,
          children: <Widget>[
            DrawerHeader(
              child: Text('Drawer Header'),
              decoration: BoxDecoration(
                color: Colors.blue,
              ),
            ),
            ListTile(
              title: Text('Item 1'),
              onTap: () {
                Navigator.pop(context);
              },
            ),
            ListTile(
              title: Text('Item 2'),
              onTap: () {
                Navigator.pop(context);
              },
            ),
          ],
        ),
      ),
    );
  }
}

#53510, #53511

@VladyslavBondarenko VladyslavBondarenko added the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Mar 30, 2020
@iKK001
Copy link
Author

iKK001 commented Mar 30, 2020

Hi Vladyslav, here is my code (i.e. most of it to get the idea).

(again, one day ago this worked fine also under iOS. It only changed behaviour after the OS-update on Mac, Xcode and iOS)

code
import 'dart:async';
import 'package:myapp_test/app_locations.dart';
import 'package:myapp_test/drawers/drawer_left.dart';
import 'package:myapp_test/drawers/drawer_right.dart';
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:myapp_test/shared/constants.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:myapp_test/screens/user_state.dart';

final webViewKey = GlobalKey<_WebViewContainerState>();

class MyWebView extends StatefulWidget {
  @override
  _MyWebViewState createState() => _MyWebViewState();
}

class _MyWebViewState extends State<MyWebView> {
  String currentPage = 'page001';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: Builder(
          builder: (context) => IconButton(
              icon: Icon(Icons.menu), // Icon(Icons.more_vert),
              onPressed: () {
                Scaffold.of(context).openDrawer();
              }),
        ),
        title: const Text(
          'My App',
          style: TextStyle(fontSize: 23.0),
        ),
        // This drop down menu demonstrates that Flutter widgets can be shown over the web view.
        actions: <Widget>[
          // IconButton(icon: Icon(Icons.search), onPressed: () async { },),
          Builder(
            builder: (context) => GestureDetector(
              onTap: () {
                Scaffold.of(context).openEndDrawer();
              },
              child: Container(
                width: 60.0,
                height: 44.0,
                child: Center(
                  child: Image.asset(
                    'assets/icons/page.png',
                    fit: BoxFit.fill,
                    height: 34.0,
                    width: 34.0,
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
      drawer: SafeArea(
        child: Transform.translate(
          offset: Offset(0.0, -50.0),
          child: ClipRRect(
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(0.0),
              topRight: Radius.circular(14.0),
              bottomLeft: Radius.circular(0.0),
              bottomRight: Radius.circular(14.0),
            ),
            child: Container(
              height: 620.0,
              child: Drawer(
                elevation: 4.0,
                child: DrawerLeft(),
              ),
            ),
          ),
        ),
      ),
      endDrawer: SafeArea(
        child: Transform.translate(
          offset: Offset(0.0, -50.0),
          child: ClipRRect(
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(14.0),
              topRight: Radius.circular(0.0),
              bottomLeft: Radius.circular(14.0),
              bottomRight: Radius.circular(0.0),
            ),
            child: Container(
              height: 620.0,
              child: Drawer(
                elevation: 4.0,
                child: DrawerRight(
                  callback: (pageType) {
                    setState(() {
                      currentPage = pageType;
                      // using currentState with question mark to ensure it's not null
                      webViewKey.currentState?.reloadWebView(pageType);
                    });
                  },
                ),
              ),
            ),
          ),
        ),
      ),
      body: WebViewContainer(key: webViewKey),
      floatingActionButton: favoriteButton(webViewKey),
    );
  }

  Widget favoriteButton(GlobalKey<_WebViewContainerState> webViewKey) {
    return Container(
      width: 500.0,
      height: 80.0,
      child: Stack(
        children: <Widget>[
          Positioned(
            bottom: 10.0,
            left: 30.0,
            child: ButtonTheme(
              shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(15.0)),
              height: 60.0,
              minWidth: 345.0,
              child: RaisedButton(
                child: Text(
                  AppLocalizations.of(context)
                      .translate('    My Personal Situation'),
                  style: TextStyle(
                      fontSize: 20.0,
                      color: Colors.white,
                      fontWeight: FontWeight.w600),
                ),
                color: Colors.blue,
                onPressed: () async {
                  return await Navigator.push(
                    context,
                    MaterialPageRoute(builder: (context) => UserState()),
                  ).then((value) {
                    setState(() {
                      webViewKey.currentState?.reloadWebView(currentPage);
                    });
                  });
                },
              ),
            ),
          ),
          Positioned(
            bottom: 18.0,
            left: 40.0,
            child: ClipRRect(
              borderRadius: BorderRadius.circular(8.0),
              child: Image.asset(
                'assets/icons/icon.png',
                fit: BoxFit.cover,
                height: 42.0,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class WebViewContainer extends StatefulWidget {
  WebViewContainer({Key key}) : super(key: key);

  @override
  _WebViewContainerState createState() => _WebViewContainerState();
}

class _WebViewContainerState extends State<WebViewContainer> {
  final Completer<WebViewController> _webViewController =
      Completer<WebViewController>();
  String myWebSide = '';

  String _getPage_001(
      String pageName, String lat, String lng, String zoom) {
    return '''
    <!DOCTYPE html>
    <html>
      <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">

        <style>
          /* Always set the map height explicitly to define the size of the div
          * element that contains the map. */
          #map {
            height: 100%;
          }
          /* Optional: Makes the sample page fill the window. */
          html, body {
            height: 100%;
            margin: 0;
            padding: 0;
          }          
        </style>
      </head>
      <body>

        <div id="map"></div>

        <script>
          const maxI = 1.0, rad = 27.0, opac = 0.6; weight = 1.0; dissipating = true;
          // const maxI = 1.0, rad = 0.01, opac = 0.6; weight = 0.5, dissipating = false;
          var map;
          var darkmode = true;
          var darkmodeStyle = [];
          var zoom = $zoom;
          var centerPos = {
            lat: $lat,
            lng: $lng
          };
          var mapOptions = {
                zoom: zoom,
                // maxZoom: 15,
                // minZoom: 12,
                center: {lat: centerPos.lat, lng: centerPos.lng},
                mapTypeId: 'roadmap',
                styles: lightStyles,
                fullscreenControl: false,
                streetViewControl: false,
                scaleControl: false,
                zoomControl: false,
          };

          function initMap() {

            // console.log(centerPos.lat, centerPos.long);
            map = new google.maps.Map(document.getElementById('map'), mapOptions);
          }

        </script>
        <script async defer
            src="https://maps.googleapis.com/maps/api/js?key=${APIKeys.googleMapsKey}&libraries=visualization&callback=initMap">
        </script>
      </body>
    </html>
    ''';
  }

  @override
  Widget build(BuildContext context) {

    return Builder(builder: (BuildContext context) {
      String contentBase64 =
          base64Encode(const Utf8Encoder().convert(myWebSide));
      return WebView(
        initialUrl: 'data:text/html;base64,$contentBase64',
        javascriptMode: JavascriptMode.unrestricted,
        onWebViewCreated: (WebViewController webViewController) {
          _webViewController.complete(webViewController);          
        },
        javascriptChannels: <JavascriptChannel>[
          _toasterJavascriptChannel(context),
        ].toSet(),
        navigationDelegate: (NavigationRequest request) {
          if (request.url.startsWith('https://www.youtube.com/')) {
            // print('blocking navigation to $request}');
            return NavigationDecision.prevent;
          }
          // print('allowing navigation to $request');
          return NavigationDecision.navigate;
        },
        onPageStarted: (String url) {
          // print('Page started loading: $url');
        },
        onPageFinished: (String url) {
          // print('Page finished loading: $url');
        },
        gestureNavigationEnabled: true,
      );
    });
  }

  void reloadWebView(String pageType) async {
    if (pageType == 'page001') {
      String pageName = 'Page001.png';
      String lat = '46.696605';
      String lng = '8.294296';
      String zoom = '7';
      myWebSide = _getPage_001(pageName, lat, lng, zoom);
    }
    // reload new webview
    setState(() {
      String contentBase64 =
          base64Encode(const Utf8Encoder().convert(myWebSide));
      _webViewController.future.then((value) => value.loadUrl('data:text/html;base64,$contentBase64'));
    });
  }
}

@no-response no-response bot removed the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Mar 30, 2020
@VladyslavBondarenko
Copy link

@iKK001
could you record your screen to show how does it look?

@VladyslavBondarenko VladyslavBondarenko added a: platform-views Embedding Android/iOS views in Flutter apps f: gestures flutter/packages/flutter/gestures repository. framework flutter/packages/flutter repository. See also f: labels. p: webview The WebView plugin plugin platform-ios iOS applications specifically labels Mar 30, 2020
@iKK001
Copy link
Author

iKK001 commented Mar 30, 2020

Below you find a video that illustrates the error behaviour:

  • All gestures and touch-event are "transparent" and make the webview underneath move instead.
  • All navigation events are ignored
  • The only working-area is the NavBar (i.e. where there is no webView)
  • If I replace the webview by a simple Text('hello') Widget instead, then everything works again...
    (problem only for iOS)

webview_error.mp4.zip

@xuexin
Copy link
Contributor

xuexin commented Mar 31, 2020

Any new information on this issue?

@DFelten
Copy link

DFelten commented Mar 31, 2020

Same problem here. On our WebView App there is a map and when selecting a point of interest, then close it, the WebView is freezed. The back button in the navigation bar is still working.

@iKK001
Copy link
Author

iKK001 commented Apr 1, 2020

Vladislav, do you have any comment on this, please ? Is it a Flutter issue or not ?

@VladyslavBondarenko
Copy link

VladyslavBondarenko commented Apr 2, 2020

@iKK001
sorry, I don't have comments about this. But I just realised that it is like this when using google map in WebView. Reproduces with dev v1.16.4-pre.33 as well on iOS 13.4

code
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final appTitle = 'Drawer Demo';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: appTitle,
      home: MyHomePage(title: appTitle),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;

  MyHomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: WebView(
        initialUrl: 'https://www.google.ee/maps',
        javascriptMode: JavascriptMode.unrestricted,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          print('pressed');
        },
        child: Icon(Icons.navigation),
        backgroundColor: Colors.green,
      ),
      drawer: Drawer(
        child: ListView(
          padding: EdgeInsets.zero,
          children: <Widget>[
            DrawerHeader(
              child: Text('Drawer Header'),
              decoration: BoxDecoration(
                color: Colors.blue,
              ),
            ),
            ListTile(
              title: Text('Item 1'),
              onTap: () {
                Navigator.pop(context);
              },
            ),
            ListTile(
              title: Text('Item 2'),
              onTap: () {
                Navigator.pop(context);
              },
            ),
          ],
        ),
      ),
    );
  }
}
  1. without using floatingActionButton, open drawer, now map responds to gestures instead of drawer's items.
  2. drag map, press floatingActionButton. After this map doesn't respond to gestures, but drawer's items do.
Screen recording

ezgif com-video-to-gif

@VladyslavBondarenko VladyslavBondarenko added customer: crowd Affects or could affect many people, though not necessarily a specific customer. severe: customer critical and removed severe: customer critical labels Apr 2, 2020
@VladyslavBondarenko VladyslavBondarenko changed the title After Mac SW update, Flutter App on iOS no longer works. [iOS][WebView] Gesture issues when using Google Map in WebView Apr 2, 2020
@VladyslavBondarenko VladyslavBondarenko added the has reproducible steps The issue has been confirmed reproducible and is ready to work on label Apr 2, 2020
@DFelten
Copy link

DFelten commented Apr 2, 2020

@VladyslavBondarenko The error also occurs with HereMaps. We use HereMaps on the page that is integrated in the WebView.

The error occurs on this page when it is integrated in a WebView on iOS 13.4: https://www.camping.info/en/search-on-map

@xuexin
Copy link
Contributor

xuexin commented Apr 2, 2020

@VladyslavBondarenko This issue also occurs with my inline web page(using webview_flutter), I think title is not correct now.

@VladyslavBondarenko VladyslavBondarenko changed the title [iOS][WebView] Gesture issues when using Google Map in WebView [iOS][WebView] Gesture issues Apr 2, 2020
@ztigunes
Copy link

@ztigunes What you described could be a different issue. I will give it a try and get back to you.

@cyanglaz, is the #66044 issue related to this?

@cyanglaz
Copy link
Contributor

cyanglaz commented Sep 18, 2020

@ztigunes The issue you are facing might be related to #66044.
This issue is related to WKWebView on iOS 13.4 and 13.5

@cyanglaz cyanglaz changed the title [iOS][WebView] Gesture issues [iOS][WebView] Gesture recognizers not blocked in iOS 13.4 and 13.5 Sep 18, 2020
@drkhannah
Copy link
Contributor

drkhannah commented Oct 1, 2020

this is happening for me as well using flutter 1.20.4 passing a gestureRecognizer to a UIKitView widget

I use the same gesture recognizer for Android and iOS, but it freezes the screen on iOS and works fine on Android, this just started happening in Flutter 1.20.1

SizedBox(
              width: double.infinity,
              height: 130,
              child: (Platform.isIOS)
                  ? UiKitView(
                      gestureRecognizers:
                          <Factory<OneSequenceGestureRecognizer>>[
                        Factory<OneSequenceGestureRecognizer>(
                          () => PlatformViewHorizontalGestureRecognizer(),
                        ),
                      ].toSet(),
                      viewType: 'Goals',
                    )
                  : AndroidView(
                      gestureRecognizers:
                          <Factory<OneSequenceGestureRecognizer>>[
                        Factory<OneSequenceGestureRecognizer>(
                          () => PlatformViewHorizontalGestureRecognizer(),
                        ),
                      ].toSet(),
                      viewType: 'Goals',
                    ),
            )),

@cyanglaz
Copy link
Contributor

cyanglaz commented Oct 7, 2020

The issue is due to the WKWebView aggressively blocking the delaying gesture recognizer to block gesture recognizers on the WKWebView on iOS 13.4 and 13.5

Unfortunately, after spending major time looking into this bug, I think this is unfixable under the current platform view architecture. The only way that I think we can fix it is to merge the UIThread and PlatformThread in flutter engine to make synchronized gesture blocking. The con of such a fix out weights the pro, especially the issue only occurs in some old iOS versions.

I'm going to close this issue because we don't have a plan to fix it for iOS 13.4 and 13.5.

@cyanglaz
Copy link
Contributor

cyanglaz commented Oct 7, 2020

We should also update the documentation of the webview plugin to indicate this limitation of the plugin on iOS 13.4 and 13.5. I opened a new issue to track this limitation. #67541

@mehmetf
Copy link
Contributor

mehmetf commented Jan 5, 2021

@cyanglaz Do we have proof that this is fixed in later iOS versions like 14.1 and 2? customer:money observed a suspiciously similar problem on iOS 14.2.

@mehmetf
Copy link
Contributor

mehmetf commented Jan 5, 2021

With the repro steps in #50420, I indeed do not see this problem on iOS 14.3; I asked customer:money to come up with a reproducible use case.

@pichillilorenzo
Copy link

Tested on a real iPhone 12 Pro and this issue persists on iOS 14.4, using webview_flutter: ^2.0.0-nullsafety.5.

After some hard debugging, I found that the problem is somehow related to javascript touch (or whatever) events added to HTML elements.

If you set javascriptMode: JavascriptMode.disabled (which is the default value), flutter widgets, such as buttons, that are over the Flutter WebView widget seem to work as expected, and the touch gestures are not propagated to the WebView (WKWebView) under this buttons.

CODE EXAMPLE with JavascriptMode.disabled
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
@override
void initState() {
  super.initState();
}

@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('Example app'),
      ),
      body: Stack(
        children: [
          WebView(
            initialUrl: "https://www.google.com",
            javascriptMode: JavascriptMode.disabled,
          ),
          Container(
            child: Column(
                mainAxisSize: MainAxisSize.min,
                mainAxisAlignment: MainAxisAlignment.end,
                children: [
                  ElevatedButton(onPressed: () {
                    print("button 1");
                  }, child: Text("TEST TEST TEST TEST TEST TEST")),
                  ElevatedButton(onPressed: () {
                    print("button 2");
                  }, child: Text("test")),
                  ElevatedButton(onPressed: () {
                    print("button 3");
                  }, child: Text("test")),
                  ElevatedButton(onPressed: () {
                    print("button 4");
                  }, child: Text("test")),
                  ElevatedButton(onPressed: () {
                    print("button 5");
                  }, child: Text("test")),
                  ElevatedButton(onPressed: () {
                    print("button 6");
                  }, child: Text("test")),
                ]),
          ),

        ],
      ),
    ),
  );
}
}
disabled.mov

Instead, if you set javascriptMode: JavascriptMode.unrestricted, (which is the value used, probably, most of the times), flutter buttons touch gestures are propagated to the Flutter WebView widget, but only on HTML elements that listen to javascript touch events (for example: addEventListener on touchstart, click, etc.).
So, If you tap on a Flutter Button that is over an HTML element that doesn't listen to any javascript touch events, the tap event doesn't propagate to the Flutter WebView.
Also, I noticed that the propagation is not instant, but only if you tap or long-press two times in the same area of the Flutter WebView:

  • the first time, you tap or long-press on a flutter button that is over an HTML element with the javascript event listener
  • the second time, you can tap or long-press everywhere in the same area.
CODE EXAMPLE with JavascriptMode.unrestricted
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
@override
void initState() {
  super.initState();
}

@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('Example app'),
      ),
      body: Stack(
        children: [
          WebView(
            initialUrl: "https://www.google.com",
            javascriptMode: JavascriptMode.unrestricted,
          ),
          Container(
            child: Column(
                mainAxisSize: MainAxisSize.min,
                mainAxisAlignment: MainAxisAlignment.end,
                children: [
                  ElevatedButton(onPressed: () {
                    print("button 1");
                  }, child: Text("TEST TEST TEST TEST TEST TEST")),
                  ElevatedButton(onPressed: () {
                    print("button 2");
                  }, child: Text("test")),
                  ElevatedButton(onPressed: () {
                    print("button 3");
                  }, child: Text("test")),
                  ElevatedButton(onPressed: () {
                    print("button 4");
                  }, child: Text("test")),
                  ElevatedButton(onPressed: () {
                    print("button 5");
                  }, child: Text("test")),
                  ElevatedButton(onPressed: () {
                    print("button 6");
                  }, child: Text("test")),
                ]),
          ),

        ],
      ),
    ),
  );
}
}
unrestricted.mov

To summarize: if a flutter widget (such as ElevatedButton) is over an HTML element that listens to any of the javascript touch events, the touch gesture (tap, long-press, ecc.) is propagated to the Flutter WebView, otherwise, it works as expected and the touch gesture is not propagated.

I'm using Flutter from "Channel master, 1.27.0-2.0.pre.43".

flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel master, 1.27.0-2.0.pre.43, on macOS 11.1 20C69 darwin-x64, locale it-IT)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 4.1)
[✓] IntelliJ IDEA Ultimate Edition (version 2020.1.2)
[✓] VS Code (version 1.52.1)
[✓] Connected device (3 available)

• No issues found!

I'm writing here because there are also people having the same problem with my InAppWebView plugin.
Hope it helps to resolve this iOS issue!

@stellarbeam
Copy link

stellarbeam commented Mar 21, 2021

If you set javascriptMode: JavascriptMode.disabled (which is the default value), flutter widgets, such as buttons, that are over the Flutter WebView widget seem to work as expected, and the touch gestures are not propagated to the WebView (WKWebView) under this buttons.

Indeed. I could reproduce this behaviour.
I'm facing the same issue on Android emulators as well as physical devices, so maybe this affects Android as well.

I tried adding a GestureDetector on top of WebView, but it does not work as expected.

I have this structure: GestureDetector (covers whole screen) > GestureDetector (covers webview) > WebView(playing YouTube embedded video)
The outer GestureDetector catches vertical drag event as expected. But neither of the GestureDetectors are able to catch the tap on WebView area even if I set the behavior to HitTestBehavior.translucent. The WebView always gets the tap.

Output of flutter doctor :

[✓] Flutter (Channel stable, 2.0.1, on Linux, locale en_US.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
[✗] Chrome - develop for the web (Cannot find Chrome executable at google-chrome)
    ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.
[✓] Android Studio
[✓] VS Code (version 1.54.2)
[✓] Connected device (1 available)

! Doctor found issues in 1 category.

@stellarbeam
Copy link

stellarbeam commented Mar 21, 2021

I tried adding a GestureDetector on top of WebView, but it does not work as expected.

Okay! So I figured out the mystery.
Upon observing the gesture arena debug logs, I noticed that the inner GestureDetector's (.e. WebView's parent's ) tap recognizer was indeed receiving the tap gesture, but WebView's tap recognizer would win everytime.

I only had to figure out a way to make sure the GestureDetector's tap recognizer always self-declares a win as soon as it receives a tap gesture.
So instead of using GestureDetector (which in turn uses the in-built tap recognizer), I made use of RawGestureDetector by giving it a custom tap recognizer.

Code for CustomTapRecognizer
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';

class CustomTapRecognizer extends OneSequenceGestureRecognizer {
  final void Function() onTap;
  Offset pointerDownPosition;

  CustomTapRecognizer({@required this.onTap});

  @override
  void addPointer(PointerEvent event) {
    if (event is PointerDownEvent) {
      startTrackingPointer(event.pointer);
      pointerDownPosition = event.position;
    } else {
      stopTrackingPointer(event.pointer);
    }
  }

  @override
  void handleEvent(PointerEvent event) {
    if (event is PointerUpEvent) {
      Offset pointerUpPosition = event.position;
      if (pointerUpPosition - pointerDownPosition == Offset.zero) {
        resolve(GestureDisposition.accepted);
      } else {
        resolve(GestureDisposition.rejected);
      }
      stopTrackingPointer(event.pointer);
    }
  }

  @override
  void acceptGesture(int pointer) {
    super.acceptGesture(pointer);
    onTap();
  }

  @override
  String get debugDescription => "tap";

  @override
  void didStopTrackingLastPointer(int pointer) {}
}

Then I used it as follows:

RawGestureDetector( // this as replacement of inner GestureDetector
  gestures: <Type, GestureRecognizerFactory>{
    CustomTapRecognizer:
        GestureRecognizerFactoryWithHandlers<CustomTapRecognizer>(
      () => CustomTapRecognizer(onTap: _onTap),
      (CustomTapRecognizer instance) {},
    )
  },
  child: SizedBox(
    child: VideoBox(_onChangeStatus), // contains WebView
    height: playerHeight,
    width: playerWidth,
  ),
),

New structure: GestureDetector > RawGestureDetector > WebView

Now suppose you have another widget over a WebView that needs to recognize a tap, say a button,
then you can create a custom widget for the button (without in-built gesture handlers) and wrap it with RawGestureDetector.

If you need other type of gestures, here are the articles that helped me understand the concepts:
Gesture disambiguation - Flutter Docs
Flutter Deep Dive: Gestures
Combining multiple GestureDetectors in Flutter

Digging into the source code documentation also helped a lot.

@pichillilorenzo
Copy link

pichillilorenzo commented Mar 22, 2021

Thanks @stellarbeam but, to me, this seems more a workaround than a solution.
I think that it should already work without implementing that. Just like it would work using Flutter widgets (non-PlatformView).
I think that everyone expects this to work as it should, that is if I put a button over another widget (that is a PlatformView or not, it shouldn't care) the button should win and capture the event (if I didn't specify some custom behaviour).

@stellarbeam
Copy link

stellarbeam commented Mar 22, 2021

Quoting @amirh , (from here)

The TapGestureRecognizer only accepts gestures as the default winner (when there are no other recognizers the claim the gesture, Dartdoc).

A PlatformViewSurface currently always contributes a member to the arena which prevents the tap detector from winning.

In some sense this is working as intended, as the tap recognizer says "I only recognizer when I know nothing else wants the touches", and a platform view that has non transparent hit test behavior is always "interested" in the touch events.

So I guess this is the behaviour that should be expected as per current gesture arena rules. But, I agree, that this behaviour is not what will be expected by users.

This behaviour is not a bug per se, because that is what rules say, but a change would definitely be helpful.

@pichillilorenzo
Copy link

Yeah, I hope this is something feasible for the Flutter team. Thanks again!

@stellarbeam
Copy link

Mention not! Happy to contribute!

@Rob-wang
Copy link

I can solve this problem by using flutter_webview_plugin. But it cannot call the display flutter component. If I use UiKitView to wrap it, will there be the same problem in iOS 13.4 and iOS 13.5?

Looking forward to your reply, it will be of great help to me. thanks

@github-actions
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 30, 2021
@flutter-triage-bot flutter-triage-bot bot added P0 Critical issues such as a build break or regression and removed P2 labels Jun 28, 2023
@flutter-triage-bot flutter-triage-bot bot added the package flutter/packages repository. See also p: labels. label Jul 5, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
a: platform-views Embedding Android/iOS views in Flutter apps c: regression It was better in the past than it is now customer: crowd Affects or could affect many people, though not necessarily a specific customer. customer: money (g3) e: OS-version specific Affects only some versions of the relevant operating system f: gestures flutter/packages/flutter/gestures repository. p: webview The WebView plugin P0 Critical issues such as a build break or regression package flutter/packages repository. See also p: labels. platform-ios iOS applications specifically
Projects
None yet
Development

No branches or pull requests