公司最近需求,有一些頁面使用了H5,主要場景是js要調(diào)用原生方法盲赊,同時(shí)原生把返回值傳個(gè)js
一、UIWebView 的 js原生交互
1敷扫、原生調(diào)用js
原生中主要代碼:
聲明一個(gè)協(xié)議
@protocol JSObjctDeleagte <JSExport>
// iOSWebView對象調(diào)用的JavaScript方法哀蘑,必須聲明!?凇绘迁!
- (NSString *)jsCallOc:(NSString *)param;
@end
在webview加載完成時(shí)
- (void)webViewDidFinishLoad:(UIWebView *)webView {
// 獲取webView的JSContext對象
self.context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//將iOSWebView對象指向自身
//js 中加入 window.iOSWebView.jsCallOc 就會(huì)調(diào)用原生里的jsCallOc方法
self.context[@"iOSWebView"] = self; //與js中保持一致
self.context[@"console"][@"log"] = ^(JSValue * msg) {
NSLog(@"H5 log : %@", msg);
};
/**
實(shí)現(xiàn)js需要調(diào)用的原生方法待js調(diào)用
*/
- (NSString *)jsCallOc:(NSString *)param {
NSLog(@"我被js調(diào)用了,并接收到參數(shù)為:%@",param);
return [NSString stringWithFormat:@"我被js調(diào)用了,js參數(shù)為:%@",param];
}
js中需要添加的代碼
//js調(diào)用oc方法
function jsCallOc(param) {
//iOSWebView js與原生約定好卒密,保持一致
var tempValue = window.iOSWebView.jsCallOc(param);//JS傳給oc的參數(shù)
alert(tempValue);
}
2缀台、原生調(diào)用js
原生oc主要代碼:
- (void)buttonAction {
// oc調(diào)用js函數(shù) js無返回值
NSString *jsAction = @"ocCallJs(11)";
[self.context evaluateScript:jsAction];
// oc調(diào)用js函數(shù) 傳參 并且有js返回值
NSString *str1 = [self.webView stringByEvaluatingJavaScriptFromString:@"jsCallOc2(oc調(diào)用js函數(shù) 并傳參 接收js返回值);"];
NSLog(@"js函數(shù)給我的返回值:%@", str1);
}
js中實(shí)現(xiàn)oc需要調(diào)用的方法
function jsCallOc2(param) {
alert(param)
return '哈哈' + param
}
到此一切看上去都很完美
但是在進(jìn)行調(diào)試的時(shí)候發(fā)現(xiàn),當(dāng)H5頁面內(nèi)部進(jìn)行頁面跳轉(zhuǎn)的時(shí)候哮奇,發(fā)現(xiàn)js 調(diào)用原生方法失效膛腐,找了網(wǎng)上的方法,很多都是說在- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 代理方法中鼎俘,remove掉之前的webview重新進(jìn)行創(chuàng)建哲身,然而并沒有解決我的問題,也沒有找到其他方法而芥,可能是我太菜??
然后就打算使用WKWebView替換UIWebView
好,接下來介紹基于WKWebView 的js原生交互
二膀值、WKWebView 的 js原生交互
js 調(diào)用原生方法
原生中的代碼大概是這樣子的:
// js配置
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
//需要JS調(diào)用iOS 原生的方法棍丐,都要添加到這里
[userContentController addScriptMessageHandler:self name:@"finishHandle"];
// WKWebView的配置
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.userContentController = userContentController;
// 顯示W(wǎng)KWebView
self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, ScreenHeight-NavitionbarHeight) configuration:configuration];
self.webView.UIDelegate = self; // 設(shè)置WKUIDelegate代理
[self.view addSubview:self.webView];
然后js 中只需
window.webkit.messageHandlers.finishHandle.postMessage('哈哈');
window.webkit.messageHandlers是固定寫法
好像也挺簡單,但是這種方式原生沒有辦法把返回值傳給js
還有在使用這種方案沧踏,在頁面消失時(shí)需要removeScriptMessageHandlerForName歌逢,否則會(huì)造成循環(huán)引用
- (void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"finishHandle"];
}
接上,下面介紹的這種方案有返回值翘狱,而且不會(huì)有重定向問題
實(shí)現(xiàn)的大致思路是秘案,讓js調(diào)用prompt()方法,然后iOS 就會(huì)調(diào)用runJavaScriptTextInputPanelWithPrompt代理方法潦匈,在代理方法中攔截js參數(shù)阱高,再進(jìn)行判斷
原生代碼:
實(shí)現(xiàn)代理方法
//接收到輸入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler {
NSError *err = nil;
NSData *dataFromString = [prompt dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *payload = [NSJSONSerialization JSONObjectWithData:dataFromString options:NSJSONReadingMutableContainers error:&err];
if (!err){
NSString *type = [payload objectForKey:@"type"];
//如果type == iOS 進(jìn)行處理,調(diào)用原生方法
if (type && [type isEqualToString:@"iOS"]){
completionHandler([self getReturnValueWithPayload:payload]);
return;
}
}
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];
}
- (void)configMethod:(NSDictionary *)payload {
NSString *returnValue = @"";
//JS調(diào)用原生方法名
NSString *functionName = [payload objectForKey:@"functionName"];
//JS傳入?yún)?shù)
NSString *args = [payload objectForKey:@"arguments"]; // JS傳入的值
//以下根據(jù)業(yè)務(wù)需要處理參數(shù)茬缩,然后返回值給js
}
js代碼:
//從移動(dòng)端獲取數(shù)據(jù)
function getAppData(functionName,param){
var webView = isWebView();
if(webView == "ios"){
var payload = {"type": "iOS", "functionName": functionName, "arguments": param};
//讓js調(diào)用prompt方法赤惊,傳入payload參數(shù),原生會(huì)接收到參數(shù)
//functionName 需要調(diào)用的原生方法
return prompt(JSON.stringify(payload));
}else{
throw new Error("環(huán)境異常凰锡!");
}
}
以上方案大致符合公司需求未舟,采用的就是這樣方案圈暗,但是這種方法有一個(gè)bug,就是在其他微信和支付寶中打開頁面會(huì)顯示彈窗