前言
之前有寫過一篇是UIWebView的js交互担猛,但是UIWebView有內(nèi)存泄露問題,并且iOS13以后將不支持UIWebView瞒窒,所以現(xiàn)在大家一般都會(huì)使用WKWebView甥桂。
現(xiàn)在總結(jié)一下WKWebView加載網(wǎng)頁還有與JS交互的方法祷杈。
加載網(wǎng)頁分為加載本地html文件和加載線上的網(wǎng)頁鏈接摊鸡。為了方便講解津坑,我這里用的是加載本地html文件的方式妙蔗。
把一個(gè)html文件導(dǎo)入到工程中
導(dǎo)入webView框架
#import <WebKit/WebKit.h>
遵循代理
<WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler>
初始化webView 設(shè)置代理
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 64, self.view.frame.size.width, self.view.frame.size.height - 64) configuration:config];
webView.navigationDelegate = self;
webView.UIDelegate = self;
向網(wǎng)頁注入js
注入js分為在網(wǎng)頁document加載完畢注入和加載之前注入
// 在加載之前注入,這時(shí)注入的js會(huì)在網(wǎng)頁的所有元素加載之前和網(wǎng)頁本身的js執(zhí)行之前執(zhí)行
WKUserScriptInjectionTimeAtDocumentStart
// 在加載之后注入国瓮,當(dāng)網(wǎng)頁的元素加載之后以及網(wǎng)頁本身的js執(zhí)行之后才會(huì)執(zhí)行注入的js灭必。
WKUserScriptInjectionTimeAtDocumentEnd
NSString *js = @"document.getElementsByTagName('h2')[0].innerText = '我是ios為h5注入的方法'";
WKUserScript *script = [[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
[config.userContentController addUserScript:script];
加載本地html文件
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSString *htmlString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
NSURL *url = [[NSURL alloc] initWithString:filePath];
[self.webView loadHTMLString:htmlString baseURL:url];
WKNavigationDelegate代理方法實(shí)現(xiàn)
// 開始加載
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
// 可以在這里做正在加載的提示動(dòng)畫 然后在加載完成代理方法里移除動(dòng)畫
}
// 網(wǎng)絡(luò)錯(cuò)誤
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
// 在這里可以做錯(cuò)誤提示
}
// 網(wǎng)頁加載完成
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
// 在這里可以移除正在加載的提示動(dòng)畫
}
WKUIDelegate代理方法實(shí)現(xiàn)
由于安全機(jī)制的問題,WKWebView默認(rèn)對(duì)JavaScript下alert類的方法(包括alert(),confirm(),prompt())做了攔截乃摹,如果要想正常使用禁漓,需要實(shí)現(xiàn)WKWebView的三個(gè)代理方法
// alert
//此方法作為js的alert方法接口的實(shí)現(xiàn),默認(rèn)彈出窗口應(yīng)該只有提示信息及一個(gè)確認(rèn)按鈕孵睬,當(dāng)然可以添加更多按鈕以及其他內(nèi)容播歼,但是并不會(huì)起到什么作用
//點(diǎn)擊確認(rèn)按鈕的相應(yīng)事件需要執(zhí)行completionHandler,這樣js才能繼續(xù)執(zhí)行
////參數(shù) message為 js 方法 alert(<message>) 中的<message>
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"確認(rèn)" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
// confirm
//作為js中confirm接口的實(shí)現(xiàn)掰读,需要有提示信息以及兩個(gè)相應(yīng)事件秘狞, 確認(rèn)及取消,并且在completionHandler中回傳相應(yīng)結(jié)果蹈集,確認(rèn)返回YES烁试, 取消返回NO
//參數(shù) message為 js 方法 confirm(<message>) 中的<message>
- (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:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(NO);
}])];
[alertController addAction:([UIAlertAction actionWithTitle:@"確認(rèn)" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(YES);
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
// prompt
//作為js中prompt接口的實(shí)現(xiàn),默認(rèn)需要有一個(gè)輸入框一個(gè)按鈕拢肆,點(diǎn)擊確認(rèn)按鈕回傳輸入值
//當(dāng)然可以添加多個(gè)按鈕以及多個(gè)輸入框减响,不過completionHandler只有一個(gè)參數(shù),如果有多個(gè)輸入框郭怪,需要將多個(gè)輸入框中的值通過某種方式拼接成一個(gè)字符串回傳支示,js接收到之后再做處理
//參數(shù) prompt 為 prompt(<message>, <defaultValue>);中的<message>
//參數(shù)defaultText 為 prompt(<message>, <defaultValue>);中的 <defaultValue>
- (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:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(alertController.textFields[0].text?:@"");
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
iOS調(diào)用js
下面的代碼是調(diào)用了js里的navButtonAction方法,并且傳入了兩個(gè)參數(shù)鄙才,回調(diào)里面response是這個(gè)方法return回來的數(shù)據(jù)颂鸿。
[self.webView evaluateJavaScript:@"navButtonAction('Jonas',25)" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
NSLog(@"%@=====%@",response,error);
}];
js調(diào)iOS
當(dāng)JS端想傳一些數(shù)據(jù)給iOS.那它們會(huì)調(diào)用下方方法來發(fā)送。
window.webkit.messageHandlers.<方法名>.postMessage(<數(shù)據(jù)>)
注意:上方代碼在JS端寫會(huì)報(bào)錯(cuò),導(dǎo)致網(wǎng)頁后面業(yè)務(wù)不執(zhí)行.可使用try-catch執(zhí)行攒庵。
在web端具體代碼如下:
try {
// 注意這里就算不想傳參數(shù) 也要傳一個(gè)空字符串''過來
window.webkit.messageHandlers.show.postMessage('我是js傳遞過來的數(shù)據(jù)');
} catch (e) {
}
在OC中的處理方法如下嘴纺。它是WKScriptMessageHandler的代理方法name和上方JS中的方法名相對(duì)應(yīng)败晴。
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
具體代碼如下:
[webView.configuration.userContentController addScriptMessageHandler:self name:@"show"];
收到j(luò)s傳來的消息時(shí)會(huì)走WKScriptMessageHandler協(xié)議方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
NSLog(@"%@",message.name);// 方法名
NSLog(@"%@",message.body);// 傳遞的數(shù)據(jù)
}
常見問題
1.控制器無法釋放問題
細(xì)心的朋友會(huì)發(fā)現(xiàn),加載WKWebView并添加js交互的控制器颖医,在退出后無法釋放掉位衩。
這是由于使用- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
方法后,把self添加為handler會(huì)導(dǎo)致self被WKWebView強(qiáng)持有熔萧,造成循環(huán)引用糖驴。所以在使用這個(gè)方法時(shí)可以將它寫在控制器將出現(xiàn)時(shí)方法中,在控制器將消失方法中再移除佛致。
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"name"];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"name"];
}
2.app端調(diào)不到j(luò)s的方法
現(xiàn)在前端開發(fā)很多都不是原生開發(fā)贮缕,而是使用vue框架。而vue框架中俺榆,在methods:這個(gè)模塊里面聲明的方法感昼,不能注入到index.js文件里。原生調(diào)用js的方法卻只能在index.js里面找罐脊,所以前端開發(fā)人員定嗓,要么把方法在index.js里面聲明,要么在methods:模塊里聲明后萍桌,再在鉤子函數(shù)mounted里面用全局的window對(duì)象來引用方法宵溅。
mounted: function() {
window.appMethod = this.appMethod;
},
methods:{
appMethod: function (string) {
console.log('app調(diào)用js方法參數(shù)=' + string);
}
}
3.js調(diào)用OC后,OC如何返回?cái)?shù)據(jù)給js上炎?
js調(diào)用OC方法后恃逻,OC是沒法像安卓端那樣直接return數(shù)據(jù)給js的,只能通過OC調(diào)用js方法藕施,將數(shù)據(jù)發(fā)送過去寇损。
4.OC如何將字典發(fā)送給js?
將字典轉(zhuǎn)化為字符串,然后傳遞給js, js那邊收到后再解析為Json即可裳食。
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
NSString * str = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSString *jsStr = [NSString stringWithFormat:@"getParamsFromNative(%@)", str];
[self.webView evaluateJavaScript:jsStr
completionHandler:^(id response, NSError * error) {
NSLog(@"response: %@, \nerror: %@", response, error);
}];
注意:如果將要傳遞的字典中某個(gè)值也為字典矛市,則需要將這個(gè)值的字典同樣轉(zhuǎn)化為字符串作為值才可以傳遞過去。
demo下載
最后為大家提供了一個(gè)簡單的demo诲祸,能夠更清晰的看到如何使用尘盼。
下載地址:
https://github.com/jiangbin1993/WKWebView-