iOS 网络(OC与JS的交互)

1,207 阅读6分钟

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 & UIWebViewOCJS交互。

它的基本原理是:

WebViewJavaScriptBridge 使用的基本步骤

  • 首先在项目中导入 WebViewJavaScriptBridge 框架
pod ‘WebViewJavascriptBridge’
  • 导入头文件 #import <WebViewJavascriptBridge.h>
  • 建立 WebViewJavaScriptBridgeWebView 的链接
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>

参考文章

WebViewJavaScriptBridge 基本使用