iOS基础 | WKWebView 使用

之前对于webview的经验只是通过URL展示H5页面,没做过交互。最近有机会接触到js交互,现在把遇到的问题进行整理。

目录:
  • 技术选型
  • 基础使用
  • 交互
  • 内存泄漏问题

UIWebView or WKWebView

iOS8之后苹果推荐使用WKWebView替代UIWebView,其优点如下:

  1. 在性能、稳定性
  2. WKWebView更多的支持HTML5的特性
  3. WKWebView更快,占用内存可能只有UIWebView的1/3 ~ 1/4
  4. WKWebView高达60fps的滚动刷新率和丰富的内置手势
  5. WKWebView具有Safari相同的JavaScript引擎
  6. WKWebView增加了加载进度属性
  7. 将UIWebViewDelegate和UIWebView重构成了14个类与3个协议官方链接

WKWebView 基础使用

// 初始化一个 WKWebViewConfiguration 对象用于配置 WKWebView
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];

// 初始化一个 WKUserContentController 对象用于js交互
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
configuration.userContentController = userContentController;

// 使用 WKWebView 的指定初始化方法 initWithFrame: configuration: 初始化 WKWebView
self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
[self.view addSubview:self.webView];

// 加载请求
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:self.url]];
[self.webView loadRequest:request];

以上代码就可以完成基础的网页展示了。需要更多操作还要设置两个代理:

@interface YPHWealthViewController ()<WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler>
self.webView.UIDelegate = self;
self.webView.navigationDelegate = self;

// TODO: 代理方法后面总结,未完待续...

交互:js 调用 OC

第一步:给前端同学一行代码,调用OC使用
window.webkit.messageHandlers.accessToken.postMessage(null) 

accessToken 即 name;
postMessage() 可以用于传参。

重要的事情说三遍!重要的事情说三遍!重要的事情说三遍!

window.webkit.messageHandlers.AppModel.postMessage(NULL或者其他参数),参数messageBody里面不能为空什么都不写,不然不会走代理方法

第二步:注册方法
// 这用到了上面提到的 WKUserContentController对象:内容交互控制器
WKUserContentController *userContentController = [WKUserContentController new];
[userContentController addScriptMessageHandler:self name:@"accessToken"];
第三步:遵循协议、实现方法
@interface YPHWealthViewController ()<WKScriptMessageHandler>
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    NSLog(@"JS 调用了 %@ 方法,传回参数 %@",message.name, message.body);
    if ([message.name isEqualToString:@"accessToken"]) {
    // 执行操作
    }
}

问题:oc如何返回数据

需求:后台希望通过调用accessToken在OC端拿到数据,可是window.webkit.messageHandlers.<name>.postMessage(<messageBody>)是没有返回值的。

解决办法:OC向js传递数据的3种方法

1.在加载请求时,使用参数
[request setHTTPBody:[token dataUsingEncoding:NSUTF8StringEncoding]];
2.使用WKUserScript

WKUserContentController是用于与JS交互的类,而所注入的JS即WKUserScript对象。使用方法如下:

NSString *sendToken = [NSString stringWithFormat:@"localStorage.setItem(\"accessToken\",'%@');", token];
WKUserScript *script = [[WKUserScript alloc] initWithSource:sendToken injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]; 
[userContentController addUserScript:script];

accessToken 是js端定义好的方法

3.使用evaluateJavaScript:completionHandler:^方法
NSString *string = [NSString stringWithFormat:@"accessToken('%@')", token];
[_webView evaluateJavaScript:string completionHandler:^(id _Nullable result, NSError * _Nullable error) {
    NSLog(@"result=%@, error=%@", result, error);
}];

// 同样,accessToken 是js端定义好的方法

总结:技术方案不同,换个思路,回归本源,从业务角度做出改变

我们最初的问题是不能给js端返回数据,但是可以主动向js端传递数据,于是我用了上面第3个方法。但是问题是,js端调用OC,OC再向js端传递数据,等我数据传递过去,js端早已经执行完了。

回到业务,js端想通过token判断我是否登录,这个token我可以提前给到js端。所以后来改用了第2个方法。下面是我画的流程图:

未命名文件.png

经过梳理,js和OC如何交互,何时交互的问题就解决了。

内存泄露问题

[userContentController addScriptMessageHandler:self name:@"accessToken"];

看博客时,很多都提到了self不能释放会导致内存泄漏,这里直接粘出解决代码如下:

@interface WeakScriptMessageDelegate : NSObject<WKScriptMessageHandler>

@property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate;

- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate;

@end

@implementation WeakScriptMessageDelegate

- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate
{
    self = [super init];
    if (self) {
        _scriptDelegate = scriptDelegate;
    }
    return self;
}

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
}

@end

使用:

YPHWeakScriptMessageDelegate *weakDelegate = [[YPHWeakScriptMessageDelegate alloc] initWithDelegate:self];
[userContentController addScriptMessageHandler:weakDelegate name:@"accessToken"];

还有,在dealloc方法中进行remove操作:

[[_webView configuration].userContentController removeScriptMessageHandlerForName:@"loginAction"];

感谢以下简书作者,以及参考链接:

WKWebView学习笔记 by 姜小骚
使用WKWebView替换UIWebView by ch32053
OC中WKWebView与js的交互 by AgoniNemo
iOS-OC与js交互:MessageHandler(userContentController代理方法不执行解决)(https://blog.csdn.net/minggeqingchun/article/details/83657353)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1.背景 WKWebView是苹果在iOS 8中引入的新组件,目的是给出一个新的高性能的WebView解决方案,摆...
    YwWyW阅读 2,746评论 6 0
  • 一、整体介绍 UIWebView自iOS2就有,WKWebView从iOS8才有,毫无疑问WKWebView将逐步...
    时间已静止阅读 19,381评论 44 92
  • iOS8之后,苹果推出了WebKit这个框架,用来替换原有的UIWebView,新的控件优点多多,不一一叙述。由于...
    TIME_for阅读 18,325评论 63 142
  • 自iOS8之后苹果推出了WKWebView,WKWebView相比于UIWebView性能更好,对于不需要支持iO...
    MrZhaoCn阅读 2,336评论 7 5
  • 人总是越活越累,随着年龄的增长烦恼也变得越来越肆无忌惮。人就是被困难烦恼所包围着的活体。都在这个孤独冷冰又存在一两...
    Sy_ab5a阅读 234评论 0 0