一个Flutter WebView侧滑bug的解决方案

2021-12-06 16:13:55 浏览数 (1)

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逻辑,这块之前就看过,本来觉得逻辑是没问题的,但是打完断点后发现判断是否退出的逻辑并没有进入。

代码语言:txt复制
// 逻辑是没有问题的,但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时使用手势的方法比较容易得出

代码语言:txt复制
onWillPop: Platform.isIOS ? null : popCallback,

到这里就可以实现iOS侧滑返回的问题,但是带来新的问题是这里的手势和WKWebView内部冲突,webview内部无法返回。

3.2 实时修改 onWillPop

顺利成章的想到根据内部是否能返回来修改onWillPop,在内部canGoBacktrue时将onWillPop置为null,而不是依赖回调事件(iOS回调事件not work)。

但找了一下webview_flutter是没有canGoBack的回调的,并且Flutter没有类似KVO的写法。

因此在iOS中实现了一下WKWebViewKVO,将一些信息canGoBack通过channel回调到Flutter。

代码语言:txt复制
#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,后续不能修改

代码语言:txt复制
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];

  }

}

0 人点赞