-
Notifications
You must be signed in to change notification settings - Fork 27.7k
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
Comments
Hi @iKK001 code
|
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) codeimport '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'));
});
}
} |
@iKK001 |
Below you find a video that illustrates the error behaviour:
|
Any new information on this issue? |
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. |
Vladislav, do you have any comment on this, please ? Is it a Flutter issue or not ? |
@iKK001 code
|
@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 |
@VladyslavBondarenko This issue also occurs with my inline web page(using webview_flutter), I think title is not correct now. |
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
|
The issue is due to the WKWebView aggressively blocking the delaying gesture recognizer to block gesture recognizers on the 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. |
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 |
@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. |
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. |
Tested on a real iPhone 12 Pro and this issue persists on iOS 14.4, using 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 CODE EXAMPLE with JavascriptMode.disabledimport '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.movInstead, if you set
CODE EXAMPLE with JavascriptMode.unrestrictedimport '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.movTo summarize: if a flutter widget (such as I'm using Flutter from "Channel master, 1.27.0-2.0.pre.43". flutter doctor
I'm writing here because there are also people having the same problem with my InAppWebView plugin. |
Indeed. I could reproduce this behaviour. I tried adding a GestureDetector on top of WebView, but it does not work as expected. I have this structure: Output of
|
Okay! So I figured out the mystery. 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. Code for CustomTapRecognizerimport '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: Now suppose you have another widget over a WebView that needs to recognize a tap, say a button, If you need other type of gestures, here are the articles that helped me understand the concepts: Digging into the source code documentation also helped a lot. |
Thanks @stellarbeam but, to me, this seems more a workaround than a solution. |
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. |
Yeah, I hope this is something feasible for the Flutter team. Thanks again! |
Mention not! Happy to contribute! |
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 |
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 |
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 alsoDrawer
(left and right) where working fine.Underneath the FloatingActionButton and Drawers is a WebView.
But after the Mac SW update:
floatingActionButton
makes a screen-freezeDrawer
shows but freezes when trying to close itI 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
:Here is my
flutter doctor -v
:edited by cyanglaz
Internal: b/155322611
The text was updated successfully, but these errors were encountered: