WKWebView
最近在学习iOS相关的知识,然后了解到UIWebView已经不能上架了,所以这篇文章还是用WKWebView去实现OC与JS的交互。WKWebView有很多明显优势:
- 更多的支持HTML5的特性
- 官方宣称的高达60fps的滚动刷新率以及内置手势
- 将UIWebViewDelegate与UIWebView拆分成了14类与3个协议,以前很多不方便实现的功能得以实现。文档
- Safari相同的JavaScript引擎
- 占用更少的内存
API介绍
WKWebView有两个delegate,WKUIDelegate 和 WKNavigationDelegate。WKNavigationDelegate主要处理一些跳转、加载处理操作,WKUIDelegate主要处理JS脚本,确认框,警告框等。因此WKNavigationDelegate更加常用。
WKNavigationDelegate的API
#pragma mark - WKNavigationDelegate
//请求之前,决定是否要跳转:用户点击网页上的链接,需要打开新页面时,将先调用这个方法。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
//接收到相应数据后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
//页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
// 主机地址被重定向时调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation;
// 页面加载完毕时调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
//跳转失败时调用
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
// 如果需要证书验证,与使用AFN进行HTTPS证书验证是一样的
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler;
//9.0才能使用,web内容处理中断时会触发
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0);
WKUIDelegate常用的API
#pragma mark - WKUIDelegate
// 提示框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"HTML的弹出框" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
// 确认框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(NO);
}])];
[alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(YES);
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
// 输入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.text = defaultText;
}];
[alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(alertController.textFields[0].text?:@"");
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
// 页面是弹出窗口 _blank 处理
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
if (!navigationAction.targetFrame.isMainFrame) {
[webView loadRequest:navigationAction.request];
}
return nil;
}
对应关系如下表格:
| JS方法 | WKUIDelegate方法 |
|---|---|
| alert(message) | -webView: runJavaScriptAlertPanelWithMessage: initiatedByFrame:completionHandler: |
| confirm(message) | -webView:runJavaScriptConfirmPanelWithMessage:initiatedByFrame:completionHandler: |
| prompt(prompt, defaultText) | -webView:runJavaScriptTextInputPanelWithPrompt:defaultText:initiatedByFrame:completionHandler: |
WKUIDelegate中的三个方法都有completionHandlerblock参数,在iOS实现对应的功能后必须调用此block完成回调,否则会崩溃。
OC与JS的交互
初始化
// 初始化configuration
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
// 下面的参数表示适配手机端,不写默认适配电脑端
NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
WKUserContentController *wkUController = [[WKUserContentController alloc] init];
[wkUController addUserScript:wkUScript];
configuration.userContentController = wkUController;
// 初始化WKWebView
self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
self.webView.navigationDelegate = self;
self.webView.UIDelegate = self;
[self.view addSubview:self.webView];
加载本地的html
// 加载一个本地的html
NSString *urlStr = [[NSBundle mainBundle] pathForResource:@"index.html" ofType:nil];
NSURL *fileURL = [NSURL fileURLWithPath:urlStr];
[self.webView loadFileURL:fileURL allowingReadAccessToURL:fileURL];
加载网络的html
NSString *urlStr = @"https://www.so.com";
NSURL *url = [NSURL URLWithString:urlStr];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
[self.webView loadRequest:request];
OC调用JS
// OC代码
// 调用JS代码
NSString *jsStr = [NSString stringWithFormat:@"showAlert('%@')",@"登陆成功"];
[self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
// 传回的信息result
NSLog(@"%@----%@",result, error);
}];
//js代码
function showAlert(messgae){
alert('我是一个可爱的弹框 \n'+messgae+'\n');
return "token";
}
打印:
2020-02-05 21:33:33.194948+0800 002---WKWebView[4974:153530] token----(null)
JS调用OC
JS调用OC有两种思路,一种是用
WKScriptMessageHandler的回调,还有一种就是用WKNavigationDelegate的拦截路由去实现
WKScriptMessageHandler回调
// OC代码
// 添加接收messgaeOC的消息
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"messgaeOC"];
}
// 移除接收messgaeOC的消息
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"messgaeOC"];
}
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
if (message.name) {
// OC 层面的消息
}
NSLog(@"message == %@ --- %@",message.name,message.body);
}
// JS代码
function messgaeHandle(){
window.webkit.messageHandlers.messgaeOC.postMessage("消息");
}
打印:
2020-02-06 10:42:42.939481+0800 002---WKWebView[856:25316] message == messgaeOC --- 消息
WKNavigationDelegate路由拦截
// OC代码
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
// 代理,用于拦截路由
NSURL *URL = navigationAction.request.URL;
NSString *scheme = [URL scheme];
if ([scheme isEqualToString:@"nhedu"]) {
NSString *host = [URL host];
if ([host isEqualToString:@"jsCallOC"]) {
NSMutableDictionary *temDict = [self decoderUrl:URL];
NSString *username = [temDict objectForKey:@"username"];
NSString *password = [temDict objectForKey:@"password"];
NSLog(@"%@---%@",username,password);
}else{
NSLog(@"不明地址 %@",host);
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
#pragma mark - 解析URL地址
- (NSMutableDictionary *)decoderUrl:(NSURL *)URL{
NSArray *params =[URL.query componentsSeparatedByString:@"&"];
NSMutableDictionary *tempDic = [NSMutableDictionary dictionary];
for (NSString *paramStr in params) {
NSArray *dicArray = [paramStr componentsSeparatedByString:@"="];
if (dicArray.count > 1) {
NSString *decodeValue = [dicArray[1] stringByRemovingPercentEncoding];
[tempDic setObject:decodeValue forKey:dicArray[0]];
}
}
return tempDic;
}
// JS代码
function submit(){
loadURL("nhedu://jsCallOC?username=Noah&password=123456");
}
打印:
2020-02-06 10:57:39.130245+0800 002---WKWebView[991:30559] Noah---123456
WebViewJavaScriptBridge基本使用
WebViewJavaScriptBridge用于WKWebView & UIWebView中OC和JS交互。
它的基本原理是:
- 首先在项目中导入
WebViewJavaScriptBridge框架
pod ‘WebViewJavascriptBridge’
- 导入头文件
#import <WebViewJavascriptBridge.h> - 建立
WebViewJavaScriptBridge和WebView的链接
self.wjb = [WebViewJavascriptBridge bridgeForWebView:self.webView];
在HTML文件中,复制粘贴这两段JS函数
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback]; // 创建一个 WVJBCallbacks 全局属性数组,并将 callback 插入到数组中。
var WVJBIframe = document.createElement('iframe'); // 创建一个 iframe 元素
WVJBIframe.style.display = 'none'; // 不显示
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'; // 设置 iframe 的 src 属性
document.documentElement.appendChild(WVJBIframe); // 把 iframe 添加到当前文导航上。
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
// 这里主要是注册 OC 将要调用的 JS 方法。
setupWebViewJavascriptBridge(function(bridge){
});
实际使用例子:
- OC调用JS
// OC代码
// 如果不需要参数,不需要回调,使用这个
// [self.wjb callHandler:@"OCCallJSFunction"];
// 如果需要参数,不需要回调,使用这个
// [self.wjb callHandler:@"OCCallJSFunction" data:@"一个字符串"];
// 如果既需要参数,又需要回调,使用这个
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self.wjb callHandler:@"OCCallJSFunction" data:@"oc调用JS咯" responseCallback:^(id responseData) {
NSLog(@"currentThread == %@",[NSThread currentThread]);
NSLog(@"调用完JS后的回调:%@",responseData);
}];
});
// JS代码
setupWebViewJavascriptBridge(function(bridge) {
// JS 被调用的方法 OCCallJSFunction 定义的标识
bridge.registerHandler('OCCallJSFunction', function(data, responseCallback) {
alert('JS方法被调用:'+data);
responseCallback('js执行过了');
})
})
打印:
2020-02-06 13:40:27.220344+0800 004---WebViewJavascriptBridge[2521:85633] currentThread == <NSThread: 0x600002376900>{number = 1, name = main}
2020-02-06 13:40:27.220435+0800 004---WebViewJavascriptBridge[2521:85633] 调用完JS后的回调:js执行过了
- JS调用OC
// OC代码
[self.wjb registerHandler:@"jsCallsOC" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"currentThread == %@",[NSThread currentThread]);
NSLog(@"data == %@ -- %@",data,responseCallback);
}];
// JS代码
function showWBJ(){
WebViewJavascriptBridge.callHandler('jsCallsOC', {'11111': '22222'}, function(response) {
alert(response);
})
}
打印:
2020-02-06 13:44:01.795031+0800 004---WebViewJavascriptBridge[2551:86830] currentThread == <NSThread: 0x60000126e900>{number = 1, name = main}
2020-02-06 13:44:01.795152+0800 004---WebViewJavascriptBridge[2551:86830] data == {
11111 = 22222;
} -- <__NSMallocBlock__: 0x60000096c510>