之前有对hybrid交互方式做了一个相关的总结,也有对UIWebView相关做了整理,相关文章如下:
花了点时间,将WKWebView做了一下整理。
1. 优点
WKWebView相比于UIWebView来说,主要有以下几个优点
- 性能上更优秀
- 与Safari相同的JS引擎
- 代理方法拆分更细,同时还提供了获取加载进度
estimatedProgress
2. 基本用法
WKWebView
的创建方法有这两种
1 | - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER; |
这里的configuration
配置很重要,可以配置js是否支持,画中画是否开启等,这里主要讲两个比较常用的属性。
websiteDataStore
用来存储WKWebView相关缓存数据的,具体增删改查就可以通过
WKWebsiteDataStore.h
中提供的方法,这里不多说,一般用的时候比较少,真的要清除缓存,简单粗暴的方法是删除沙盒目录中的Cache文件夹。userContentController
js和oc的交互,以及注入js代码都会用到它,查看
WKUserContentController
的头文件,你会发现它有如下几个方法:1
2
3
4
5
6
7
8
9
10
11
12@interface WKUserContentController : NSObject <NSCoding>
//读取添加过的脚本
@property (nonatomic, readonly, copy) NSArray<WKUserScript *> *userScripts;
//添加脚本
- (void)addUserScript:(WKUserScript *)userScript;
//删除所有添加的脚本
- (void)removeAllUserScripts;
//通过window.webkit.messageHandlers.<name>.postMessage(<messageBody>) 来实现js->oc传递消息,并添加handler
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
//删除handler
- (void)removeScriptMessageHandlerForName:(NSString *)name;
@end通过给
userContentController
添加WKUserScript
,可以实现动态注入js。比如注入一个脚本1
2
3//注入一个Cookie
WKUserScript *newCookieScript = [[WKUserScript alloc] initWithSource:@"document.cookie = 'DarkAngelCookie=DarkAngel;'" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[controller addUserScript:newCookieScript];
加载一个请求或者页面,如下:
1 | - (nullable WKNavigation *)loadRequest:(NSURLRequest *)request; |
代理方面,提供了两个代理
- WKNavigationDelegate 类似于UIWebView的加载成功、失败、是否允许跳转等
- WKUIDelegate 主要是一些alert、打开新窗口之类的
1 | //下面这2个方法共同对应了UIWebView的 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; |
属性方面,WKWebView.h
定义了如下几个常用的readonly
属性:
1 | @property (nullable, nonatomic, readonly, copy) NSString *title; //页面的title,终于可以直接获取了 |
这些属性都很有用,而且支持KVO,所以我们可以通过KVO观察这些值的变化,以便于我们做出最友好的交互。
3. WKWebView中js与oc的交互
3.1 OC调用JS
WKWebView
提供了一个类似JavaScriptCore
的方法:evaluateJavaScript
,这个和UIWebView一样的
1 | [self.webView evaluateJavaScript:@"document.title" completionHandler:^(id _Nullable title, NSError * _Nullable error) { |
可以参考之前写的UIWebView相关整理篇,就不多说了。
3.2 JS调用oc
3.2.1 假跳转的请求拦截的方式
和UIWebView的拦截方式一样的,只不过走的代理方法不同而已。
1 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { |
该方案的具体优缺点,以及回调的问题,和UIWebView里是一样的,这里也不多说了,可以参考之前写的UIWebView相关整理篇
3.2.2 弹框拦截
- alert():alert框
- prompt():文本输入框
- confirm():确认框
JS端在调用弹框方法的时候,会触发WKWebView中对应的弹框代理回调
1 | - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler; |
注意:如果对应代理方法默认不写的话,是不会响应对应的弹框的
1 |
|
其中completionHandler
这个block 一定得调用,至于在哪里调用,倒是无所谓,我们也可以写在方法实现的第一行,或者最后一行。
在这里,当js调用了弹框,我们在对应的弹框代理中可以获取到js传递过来的相关参数,然后根据这些参数做操作。
在第三方库DSBridge
中,其实交互思路就是根据弹框来做的,并且提供了同步异步交互,弹框的本质就是同步,在DSBridge
中直接主线程执行返回的,而异步的话,DSBridge
中其实是放在一个子线程里做了个延迟执行,然后通过OC中弹框的Block来调用JS的callBack进行回调。
3.2.3 注入的方式
苹果scriptMessageHandler注入
1 | - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name; |
具体使用方式:
在oc中注入添加handler
1 | //配置对象注入 |
在js中调用
1 | //准备要传给native的数据,包括指令,数据,回调等 |
此时在oc中会触发回调的delegate
1 | - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { |
当然,记得在适当的地方调用removeScriptMessageHandler
1 | - (void)dealloc { |
这里又涉及到了一些问题
- js如何获取返回值
- 通过
window.webkit.messageHandlers..postMessage()
传递的messageBody中不能包含js的function,如果包含了function,那么 OC端将不会收到回调。
之前在UIWebView整理中,其实也提过这个回调,主要是通过执行js的callBack,但是在这里第二个问题中,我们可以把js的function转换为字符串,再传递给OC。
场景:h5中有一个分享按钮,用户点击之后,调用native分享(微信分享、微博分享等),在native分享成功或者失败时,回调h5页面,告诉其分享结果,h5页面刷新对应的UI,显示分享成功或者失败。
1 | /** |
在oc中:
1 | //首先别忘了,在configuration中的userContentController中添加scriptMessageHandler |