前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一个Flutter WebView侧滑bug的解决方案

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

原创
作者头像
晨之阴影
修改2021-12-06 16:13:55
2.9K1
修改2021-12-06 16:13:55
举报
文章被收录于专栏:晨光的Code晨光的Code晨光的Code

1. 问题表现

iOS版本的侧滑返回不生效,只能在页面内侧滑返回二级web页面,不能Pop整个WebView.

2. 问题定位

2.1 猜测WebView内部手势和外部手势冲突

首先猜测是不是内部的滑动手势跟外部的冲突,因此找到了iOS WKWebView管理内部侧滑的API。

self.webview.allowsBackForwardNavigationGestures = true;

而在Flutter中对应的API则是webview的初始化参数

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,在内部canGoBacktrue时将onWillPop置为null,而不是依赖回调事件(iOS回调事件not work)。

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

因此在iOS中实现了一下WKWebViewKVO,将一些信息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入口

/// 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原生解决方法

- (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];

  }

}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 问题表现
  • 2. 问题定位
    • 2.1 猜测WebView内部手势和外部手势冲突
      • 2.2 Review Flutter侧代码
      • 3. 解决方案
        • 3.1 WillPopScope与手势怎么共存
          • 3.2 实时修改 onWillPop
            • 3.3 峰回路转
              • 附录:iOS原生解决方法
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档