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代码
WKWebView 复制代码
Native代码
//UIWebViewNSString *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代码
WKWebView 复制代码
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() { var num = addNum(5, 8); console.log(num); 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:idname:@"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的方法
// Javascriptfunction callNative() { window.webkit.messageHandlers.MyNative.postMessage('body');}复制代码
相关的JS和iOS代码已上传至GitHub(),可自行下载和调试。