Web技术相对于Native来说有很多优势,比如:跨端(浏览器、Android、iOS)、排版更灵活、实时生效等。所以,在开发中我们经常会采用一些Web页面嵌入到APP中。
这样,就引入了web与Native的交互,往往也就是JavaScript与Native的交互。
JS与Native的交互,可以大致分为:Native调用JS、JS调用Native。
Native调用JS
Native调用JS比较简单
//UIWebView
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
//WKWebView
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
复制代码
注意:
- UIWebView执行JS是同步的,在WKWebView中是异步的。
- UIWebView返回值只支持可String化的基本类型,并不支持集合类型(Array、Dictionary)和对象。
- WKWebView执行JS的回调Block中支持集合类型和对象,也支持可String化的基本类型。
h5代码
<!DOCTYPE html>
<html>
<head>
<title>WKWebView</title>
<meta charset="UTF-8" />
<script type="text/javascript">
function methodA(username, password) {
console.log("Hello, I am methodA");
let dict = {
"username" : username,
"password" : password
};
return "username: " + username + ", password: " + password;
<!--return dict;-->
<!--return ["A", "B"];-->
}
</script>
</head>
<body>
</body>
</html>
复制代码
Native代码
//UIWebView
NSString *str = [self.webView stringByEvaluatingJavaScriptFromString:@"methodA('zhangsan', '123456')"];
NSLog(@"str");
//WKWebView
[self.webView evaluateJavaScript:@"methodA('zhangsan', '123456')" completionHandler:^(id _Nullable value, NSError * _Nullable error) {
if (error) {
NSLog(@"%@", error);
return ;
}
NSLog(@"%@", value);
}];
复制代码
JS调用Native
其实,JS调用Native的交互经历过几个阶段。
iOS7之前
iOS7之前,JS想要调用Native时主动触发加载一个新的url。我们通过拦截URL通过提前约定的协议来判断是否为调用原生功能,用这种方式来处理JS对Native的调用。
JS端主要通过window.location.assign
来触发新的加载。
iOS中在- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
中针对URL做判断。
JS代码
<!DOCTYPE html>
<html>
<head>
<title>WKWebView</title>
<meta charset="UTF-8" />
<script type="text/javascript">
function callNative() {
window.location.assign("native://func1?name=zhangsan&password=123456");
}
</script>
</head>
<body>
<button onClick="callNative()">CallNative</button>
</body>
</html>
复制代码
OC代码
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSString *urlPath = [request.URL absoluteString];
/*
* 与Web端协商定义交互协议 scheme://function_name?para1=value1¶2=value2¶3=value3&callback=callback_value
* scheme为native时,是要与原生进行交互。
* function_name指定要调用原生的什么功能。
* para1是参数名称,value1是参数值
* callback是原生执行完后对js的回调
*/
if ([urlPath hasPrefix:@"native://"]) {
urlPath = [urlPath substringFromIndex:@"native://".length];
NSArray *questionMarkArray = [urlPath componentsSeparatedByString:@"?"];
NSString *funcName;
NSArray *paraArray;
if (questionMarkArray.count > 0) {
funcName = [questionMarkArray firstObject];
if (questionMarkArray.count > 1) {
// 从问号后还是都认定为参数
NSString *paraStr = [urlPath substringFromIndex:funcName.length + 1];
paraArray = [paraStr componentsSeparatedByString:@"&"];
}
}
// funcname 匹配
if ([funcName isEqualToString:@"func1"]) {
} else if ([funcName isEqualToString:@"func2"]) {
} else if ([funcName isEqualToString:@"func3"]) {
} else {
}
return NO;
} else {
return YES;
}
}
复制代码
iOS7
iOS7开始,系统公开了JavaScriptCore框架,我们可以基于此来进行JS与Natvie的交互。
插播Javascript的调试方法(我们可以在Mac上调试JS代码)。
Safari的偏好设置中勾选底部的在菜单栏中显示“开发”菜单,然后Safari的菜单栏中就多出了开发菜单,当webview加载完h5后就可以选择对应的模拟器下对应的应用进行web调试。详细步骤和真机的调试方法请自行Google。
iOS7中通常采用的方式是拿到JSContext,然后将OC的方法或对象注入到JS中。
- (void)webViewDidFinishLoad:(UIWebView *)webView {
self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// JS的异常回调
self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"%@", exception);
};
// 注入方法
self.jsContext[@"addNum"] = ^(int a, int b) {
return a + b;
};
// 注入对象
NSDictionary *dict = @{@"aaa" : @"aaa", @"bbb" : @"bbb"};
self.jsContext[@"dict"] = [JSValue valueWithObject:dict inContext:self.jsContext];
}
复制代码
然后在JS中就可以调用相应的对象和方法,调用方式和调用原生的方法或对象一致。
function callByContext() {
<!-- addNum为原生向JS注入的方法 -->
var num = addNum(5, 8);
console.log(num);
<!-- dict为原生向JS注入的对象 -->
console.log(dict);
}
复制代码
iOS8 WKWebView
UIWebView有很多性能问题,所以苹果在iOS8中引入了新的浏览器组件WKWebView。
并且在iOS12后苹果废弃了UIWebView,所以请同学们务必赶紧升级到WKWebView!!!
在WKWebView的交互中,要使用WKUserContentController。在WKWebView构建的时候,需要传入WKWebViewConfiguration,而WKWebViewConfiguration可以添加WKUserContentController为ScriptMessageHandler。
WKUserContentController *userContent = [[WKUserContentController alloc] init];
//伪代码
[userContent addScriptMessageHandler:id<WKScriptMessageHandler> name:@"MyNative"];
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.userContentController = userContent;
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
复制代码
handler 对象需要实现WKScriptMessageHandler协议,当 JS 端通过 window.webkit.messageHandlers
发送 Native 消息时,handler 对象的协议方法被调用,通过协议方法的相关参数传值。
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {}
复制代码
JS调用iOS的方法
// Javascript
function callNative() {
window.webkit.messageHandlers.MyNative.postMessage('body');
}
复制代码
相关的JS和iOS代码已上传至GitHub(github.com/NewFarmer21…),可自行下载和调试。