1. 问题表现
iOS版本的侧滑返回不生效,只能在页面内侧滑返回二级web页面,不能Pop整个WebView.
2. 问题定位
2.1 猜测WebView内部手势和外部手势冲突
首先猜测是不是内部的滑动手势跟外部的冲突,因此找到了iOS WKWebView管理内部侧滑的API。
代码语言:txt复制self.webview.allowsBackForwardNavigationGestures = true;
而在Flutter中对应的API则是webview的初始化参数
代码语言:txt复制child: WebView(
gestureNavigationEnabled: true,
)
但是改为false之后确实禁用了内部的侧滑返回,但是整个webview的侧滑返回依然有问题。
2.2 Review Flutter侧代码
在Flutter中发现了web_view.dart
中关于侧滑返回和点击返回的WillPopScope
逻辑,这块之前就看过,本来觉得逻辑是没问题的,但是打完断点后发现判断是否退出的逻辑并没有进入。
// 逻辑是没有问题的,但iOS侧滑手势并不会进入这个方法
// https://github.com/flutter/flutter/issues/14203
// github中的issue也一直没有关闭
// 猜测原因是iOS中的侧滑是是一个同步的手势,并没有时机去执行异步callback,具体需要看下源码,待补充
Future<bool> _exit() async {
//iOS咋不进去 —— Jonny也发现了
if (!needH5Back) {
return Future.value(true);
}
return _controller.future.then((controller) async {
if (await controller.canGoBack() == true) {
await controller.goBack();
return Future.value(false);
} else {
return Future.value(true);
}
});
}
因此在此就确定了是flutter侧的问题
3. 解决方案
3.1 WillPopScope与手势怎么共存
在使用WillPopScope
时使用手势的方法比较容易得出
onWillPop: Platform.isIOS ? null : popCallback,
到这里就可以实现iOS侧滑返回的问题,但是带来新的问题是这里的手势和WKWebView内部冲突,webview内部无法返回。
3.2 实时修改 onWillPop
顺利成章的想到根据内部是否能返回来修改onWillPop
,在内部canGoBack
为true
时将onWillPop
置为null,而不是依赖回调事件(iOS回调事件not work)。
但找了一下webview_flutter
是没有canGoBack
的回调的,并且Flutter没有类似KVO的写法。
因此在iOS中实现了一下WKWebView
的KVO
,将一些信息canGoBack
通过channel回调到Flutter。
#pragma mark KVO
- (void)addKVO {
[_webView addObserver:self forKeyPath:@"canGoBack" options:NSKeyValueObservingOptionNew context:nil];
[_webView addObserver:self forKeyPath:@"canGoForward" options:NSKeyValueObservingOptionNew context:nil];
[_webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
[_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)removeKVO {
[_webView removeObserver:self forKeyPath:@"canGoBack"];
[_webView removeObserver:self forKeyPath:@"canGoForward"];
[_webView removeObserver:self forKeyPath:@"title"];
[_webView removeObserver:self forKeyPath:@"estimatedProgress"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"canGoBack"]) {
[_navigationDelegate canGoBackStateChanged:_webView.canGoBack];
} else if ([keyPath isEqualToString:@"canGoForward"]) {
[_navigationDelegate canGoForwardStateChanged:_webView.canGoForward];
} else if ([keyPath isEqualToString:@"title"]) {
[_navigationDelegate titleChanged:_webView.title];
} else if ([keyPath isEqualToString:@"estimatedProgress"]) {
[_navigationDelegate estimatedProgressChanged:_webView.estimatedProgress];
}
}
#pragma mark - WebViewInfo KVO
- (void)canGoBackStateChanged:(BOOL)canGoBack {
[_methodChannel invokeMethod:@"canGoBackStateChanged" arguments:@{@"canGoBack" : [NSNumber numberWithBool:canGoBack]}];
}
- (void)canGoForwardStateChanged:(BOOL)canGoForward {
[_methodChannel invokeMethod:@"canGoForwardStateChanged" arguments:@{@"canGoForward" : [NSNumber numberWithBool:canGoForward]}];
}
- (void)titleChanged:(NSString *)title {
[_methodChannel invokeMethod:@"titleChanged" arguments:@{@"title" : title}];
}
- (void)estimatedProgressChanged:(double)progress {
[_methodChannel invokeMethod:@"estimatedProgressChanged" arguments:@{@"progress" : [NSNumber numberWithDouble:progress]}];
}
并且增加了WebView的callback入口
代码语言:txt复制/// Invoked by [WebView] when a page canGoBack State Changed.
final CanGoBackStateChangedCallback canGoBackStateChanged;
/// Invoked by [WebView] when a page canGoForward State Changed.
final CanGoForwardStateChangedCallback canGoForwardStateChanged;
/// Invoked by [WebView] when a page title Changed.
final TitleChangedCallback titleChanged;
/// Invoked by [WebView] when a page estimatedProgress Changed.
final EstimatedProgressChangedCallback estimatedProgressChanged;
但是一切都改完之后发现onWillPop
是filial,后续不能修改
final WillPopCallback onWillPop;
3.3 峰回路转
网上到处都没有找到解决方案,接了一个号称能解决的组件也并不work,因此只能回过头了再看代码和文档。
搜索了一下ModalRoute的方法,发现是有一个动态的数组来存储callback,只要把数组里的callback移除,就跟onwillpop置为null的效果是一样的,因此最终可以动态化的来进行修改。
附录:iOS原生解决方法
代码语言:txt复制- (void)addKVO {
[self.mpWebview addObserver:self forKeyPath:@"canGoBack" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)removeKVO {
[self.mpWebview removeObserver:self forKeyPath:@"canGoBack"];
}
- (void)dealloc {
[self removeKVO];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"canGoBack"]) {
self.mpWebview.allowsBackForwardNavigationGestures = self.mpWebview.canGoBack;
[self.navigationController.interactivePopGestureRecognizer setEnabled:!self.mpWebview.canGoBack];
}
}