最近公司的运营瞎搞了个活动,其活动要服务端提供数据支持,web前端在微信公众帐号内做为主要的运营阵地,而iOS、Android要提供相应的入口及页面进行配合。一个活动,动用了各个端的程序猿。而在这里面技术方面主要就是涉及到web端和服务端的交互,web前端和iOS、Android的交互。本人做为一个iOS开发者,今天就聊聊web、iOS、Android三端的交互,其实在说明白一点就是方法的互相调用而已。这里主要讲解iOS。Android会稍微提一下,仅做参考。html
此篇文章的逻辑图前端
图0-0 此篇文章的逻辑图java
概述git
iOS原生应用和web页面的交互大体上有这几种方法iOS7以后的JavaScriptCore、拦截协议、第三方框架WebViewJavaScriptBridge、iOS8以后的WKWebView在这里主要讲解JavaScriptCore和拦截协议这两种办法。WebViewJavaScriptBridge是基于拦截协议进行的封装。学习成本相对JavaScriptCore较高,使用也不如JavaScriptCore方便本文不作叙述。WKWebView是iOS8以后推出的,尚未成为主流使用,因此本篇文章也不作详细叙述。github
Objective-C执行JavaScript代码web
相关方法缓存
1微信 2app 3框架 4 5 6 |
|
相关应用
用这些方法去执行大段的JavaScript代码是没什么必要的,可是有些小场景用起来仍是比较顺手和实用的,列举两个例子做为参考:
1 2 3 4 5 |
|
JavaScriptCore
iOS7以后苹果推出了JavaScriptCore这个框架,从而让web页面和本地原生应用交互起来很是方便,并且使用此框架能够作到Android那边和iOS相对统一,web前端写一套代码就能够适配客户端的两个平台,从而减小了web前端的工做量。
web前端
在三端交互中,web前端要强势一些,一切传值、方法命名都按web前端开发人员来定义,让另外两端去作适配。在这里以调用摄像头和分享为例来详细讲解,测试网页代码取名为test.html,其代码内容以下:
test.html代码内容(因识别问题,用方括号替换了代码中的尖括号)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
test.html代码解释
可能有些同窗对web前端的一些知识不太熟悉,稍微对这段代码作下解释,先说Toyun是iOS和Android这两边在本地要注入的一个对象【参考下面iOS的代码更容易明白】,充当原生应用和web页面之间的一个桥梁。页面上定义了两个按钮名字分别为CallCamera和Share。点击CallCamera会经过Toyun这个桥梁调用本地应用的方法- (void)callCamera,没有传参;而点击Share会先调用本文件中的JavaScript方法callShare这里将要分享的内容格式转成JSON字符串格式(这样作是为了适配Android,iOS能够直接接受JSON对象)而后再经过Toyun这个桥梁去调用原生应用的- (void)share:(NSString *)shareInfo方法这个是有传参的,参数为shareInfo。而下面的两个方法为原生方法调用后的回调方法,其中picCallback为获取图片成功的回调方法,而且传回拿到的图片photos;shareCallback为分享成功的回调方法。
iOS
iOS这边根据前端定义的方法名来写代码,可是有些时候web前端会让咱们定义,可是咱们定义好以后他又要修改,这时候就会很烦啊。因此碰到三端交互的时候最好就是让web前端去定义方法名,iOS和Android根据web前端定义好的去写代码。JavaScriptCore中web页面调用原生应用的方法能够用Delegate或Block两种方法,此文以按Delegate讲解。
JavaScriptCore中类及协议:
JSContext:给JavaScript提供运行的上下文环境
JSValue:JavaScript和Objective-C数据和方法的桥梁
JSManagedValue:管理数据和方法的类
JSVirtualMachine:处理线程相关,使用较少
JSExport:这是一个协议,若是采用协议的方法交互,本身定义的协议必须遵照此协议
ViewController中的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
|
ViewController中的代码解释
自定义JSObjcDelegate协议,并且此协议必须遵照JSExport这个协议,自定义协议中的方法就是暴露给web页面的方法。在webView加载完毕的时候获取JavaScript运行的上下文环境,而后再注入桥梁对象名为Toyun,承载的对象为self即为此控制器,控制器遵照此自定义协议实现协议中对应的方法。在JavaStript调用完本地应用的方法作完相对应的事情以后,又回调了JavaStript中对应的方法,从而实现了web页面和本地应用之间的通信。
JavaScriptCore使用注意
JavaStript调用本地方法是在子线程中执行的,这里要根据实际状况考虑线程之间的切换,而在回调JavaScript方法的时候最好是在刚开始调用此方法的线程中去执行那段JavaStript方法的代码,我在实际运用中开始没注意,就被坑惨了啊。什么,说的太绕,看下面的代码解释:
1 2 3 4 5 6 7 8 9 |
|
运行效果
运行效果如图3-1所示
图3-1 运行效果
拦截协议
拦截协议这个适合一些比较简单的一些状况,不须要引入什么框架,只须要web前端配合一下就好。可是在具体调用哪个方法上,以及在传值的时候可能会有些不方便,并且调用完后没法在回调JavaScript的方法。
web前端
test.html中的代码(因识别问题,用方括号替换了代码中的尖括号)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
test.html中的代码解释
这段代码相比上面的那段测试代码是很简单的,一样有一个按钮,名字为CallCamera点击以后调用本身的callCamera方法,window.location.href这里是改变主窗口的指向从而立刻发出一个连接为Toyun://callCamera请求,而想要传给原生应用的参数也可已包含到此请求中,而在iOS方法中咱们要拦截这个请求,根据请求内容去判断JavaStript想要作的事情,从而实现web页面和本地应用之间的交互。
iOS
iOS对应的代码
1 2 3 4 5 6 7 8 9 10 |
|
iOS对应的代码的解释
在webView的代理方法中去拦截自定义的协议Toyun://若是是此协议则据此判断JavaStript想要作的事情,调用原生应用的方法,这些都是提早约定好的,同时阻止此连接的跳转。
总结
随着手机硬件的配置愈来愈强大和HTML5的兴起,一个App彻底能够由web页面来写。如今已经有部分应用这么干了,我是碰见过的,如古诗文网。尽管比较少可是web页面和本地应用的交互不管是iOS仍是Android都是会有遇到的。iOS我仍是比较推荐JavaScriptCore,这样三端能够相对统一块儿来,写的时候都比较简单。随着时间的推移iOS8推出的WKWebView会逐渐成为主流,这个的功能更强大。拦截协议也只能说用到比较简单的一些状况吧,复杂的状况处理相互之间参数的传递仍是比较麻烦的,并且这个不能回调JavaScript的方法,确实喜欢拦截协议的同窗能够研究WebViewJavaScriptBridge这个第三方库。对于Android本人也就是略知皮毛而已,就不班门弄斧了,对于一些Android开发者来讲,能够看地第一段的test.html这个页面的写法彻底是能够适配Android的。
做者 TIME_for 关注
2016.11.19 16:31* 字数 2498 阅读 4996评论 54喜欢 97
iOS8
以后,苹果推出了WebKit
这个框架,用来替换原有的UIWebView
,新的控件优势多多,不一一叙述。因为一直在适配iOS7
,就没有去替换,如今仍掉了iOS7
,觉得很简单的就替换过来了,然而在替换的过程当中,却遇到了不少坑。还有一点就是原来写过一篇文章 Objective-C与JavaScript交互的那些事觉得年代久远的UIWebView
已经做古,可这篇文章如今依然有必定的阅读量。因此在决定在续一篇此文,以引导你们转向WKWebView
,并指出本身踩过的坑,让你们少走弯路。
此篇文章的逻辑图
此篇文章的逻辑图
使用及注意点
WKWebView
只能用代码建立,并且自身就支持了右滑返回手势allowsBackForwardNavigationGestures
和加载进度estimatedProgress
等一些UIWebView
不具有却很是好用的属性。在建立的时候,指定初始化方法中要求传入一个WKWebViewConfiguration
对象,通常咱们使用默认配置就好,可是有些地方是要根据本身的状况去作更改。好比,配置中的allowsInlineMediaPlayback
这个属性,默认为NO
,若是不作更改,网页中内嵌的视频就没法正常播放。
更改User-Agent
有时咱们须要在User-Agent
添加一些额外的信息,这时就要更改默认的User-Agent
在使用UIWebView
的时候,可用以下代码(在使用UIWebView
以前执行)全局更改User-Agent
:
// 获取默认User-Agent UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero]; NSString *oldAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"]; // 给User-Agent添加额外的信息 NSString *newAgent = [NSString stringWithFormat:@"%@;%@", oldAgent, @"extra_user_agent"]; // 设置global User-Agent NSDictionary *dictionnary = [[NSDictionary alloc] initWithObjectsAndKeys:newAgent, @"UserAgent", nil]; [[NSUserDefaults standardUserDefaults] registerDefaults:dictionnary];
以上代码是全局更改User-Agent
,也就是说,App
内全部的Web
请求的User-Agent
都被修改。替换为WKWebView
后更改全局User-Agent
能够继续使用上面的一段代码,或者改成用WKWebView
获取默认的User-Agent
,代码以下:
self.wkWebView = [[WKWebView alloc] initWithFrame:CGRectZero]; // 获取默认User-Agent [self.wkWebView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) { NSString *oldAgent = result; // 给User-Agent添加额外的信息 NSString *newAgent = [NSString stringWithFormat:@"%@;%@", oldAgent, @"extra_user_agent"]; // 设置global User-Agent NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:newAgent, @"UserAgent", nil]; [[NSUserDefaults standardUserDefaults] registerDefaults:dictionary]; }];
对比发现,这两种方法并无本质的区别,一点小区别在于一个是用UIWebView
获取的默认User-Agent
,一个是用WKWebView
获取的默认User-Agent
。上面方法的缺点也是很明显的,就是App
内全部Web
请求的User-Agent
所有被修改。
在iOS9
,WKWebView
提供了一个很是便捷的属性去更改User-Agent
,就是customUserAgent
属性。这样使用起来不只方便,也不会全局更改User-Agent
,惋惜的是iOS9
才有,若是适配iOS8
,仍是要使用上面的方法。
WKWebView的相关的代理方法
WKWebView
的相关的代理方法分别在WKNavigationDelegate
和WKUIDelegate
以及WKScriptMessageHandler
这个与JavaScript
交互相关的代理方法。
WKNavigationDelegate
: 此代理方法中除了原有的UIWebView
的四个代理方法,还增长了其余的一些方法,具体可参考我下面给出的Demo
。WKUIDelegate
: 此代理方法在使用中最好实现,不然遇到网页alert
的时候,若是此代理方法没有实现,则不会出现弹框提示。WKScriptMessageHandler
: 此代理方法就是和JavaScript
交互相关,具体介绍参考下面的专门讲解。WKWebView下面添加自定义View
由于咱们有个需求是在网页下面在添加一个View
,用来展现此连接内容的相关评论。在使用UIWebView
的时候,作法很是简单粗暴,在UIWebView
的ScrollView
后面添加一个自定义View
,而后根据View
的高度,在改变一下scrollView
的contentSize
属性。觉得WKWebView
也能够这样简单粗暴的去搞一下,结果却并非这样。
首先改变WKWebView
的scrollView
的contentSize
属性,系统会在下一次帧率刷新的时候,再给你改变回原有的,这样这条路就行不通了。我立刻想到了另外一个办法,改变scrollView
的contentInset
这个系统倒不会在变化回原来的,自觉得完事大吉。后来过了两天,发现有些页面的部分区域的点击事件没法响应,百思不得其解,最后想到多是设置的contentInset
对其有了影响,事实上正是如此。查来查去,最后找到了一个解决办法是,就是当页面加载完成时,在网页下面拼一个空白的div
,高度就是你添加的View
的高度,让网页多出一个空白区域,自定义的View
就添加在这个空白的区域上面。这样就完美解决了此问题。具体可参考Demo
所写,核心代码以下:
self.addView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, addViewHeight)]; self.addView.backgroundColor = [UIColor redColor]; [self.webView.scrollView addSubview:self.addView]; NSString *js = [NSString stringWithFormat:@"\ var appendDiv = document.getElementById(\"AppAppendDIV\");\ if (appendDiv) {\ appendDiv.style.height = %@+\"px\";\ } else {\ var appendDiv = document.createElement(\"div\");\ appendDiv.setAttribute(\"id\",\"AppAppendDIV\");\ appendDiv.style.width=%@+\"px\";\ appendDiv.style.height=%@+\"px\";\ document.body.appendChild(appendDiv);\ }\ ", @(addViewHeight), @(self.webView.scrollView.contentSize.width), @(addViewHeight)]; [self.webView evaluateJavaScript:js completionHandler:nil];
WKWebView加载HTTPS的连接
HTTPS
已经愈来愈被重视,前面我也写过一系列的HTTPS
的相关文章HTTPS从原理到应用(四):iOS中HTTPS实际使用当加载一些HTTPS
的页面的时候,若是此网站使用的根证书已经内置到了手机中这些HTTPS
的连接能够正常的经过验证并正常加载。可是若是使用的证书(通常为自建证书)的根证书并无内置到手机中,这时是连接是没法正常加载的,必需要作一个权限认证。开始在UIWebView
的时候,是把请求存储下来而后使用NSURLConnection
去从新发起请求,而后走NSURLConnection
的权限认证通道,认证经过后,在使用UIWebView
去加载这个请求。
在WKWebView
中,WKNavigationDelegate
中提供了一个权限认证的代理方法,这是权限认证更为便捷。代理方法以下:
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([challenge previousFailureCount] == 0) { NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; completionHandler(NSURLSessionAuthChallengeUseCredential, credential); } else { completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); } } else { completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); } }
这个方法比原来UIWebView
的认证简单的多。可是使用中却发现了一个很蛋疼的问题,iOS8
系统下,自建证书的HTTPS
连接,不调用此代理方法。查来查去,原来是一个bug
,在iOS9
中已经修复,这明显就是无论iOS8
的状况了,并且此方法也没有标记在iOS9
中使用,这点让我感到有点失望。这样我就又想到了换回原来UIWebView
的权限认证方式,可是试来试去,发现也不能使用了。因此关于自建证书的HTTPS
连接在iOS8
下面使用WKWebView
加载,我没有找到很好的办法去解决此问题。这样我不得已有些连接换回了HTTP
,或者在iOS8
下面在换回UIWebView
。若是你有解决办法,也欢迎私信我,感激涕零。
WKWebView加载POST请求
很是感谢@e231e1ff5f8b的指出,原来POST
请求这儿还有一个坑。本身项目中并无这块需求,也就没有发现。加载POST
请求的时候,会丢失HTTPBody
。解决办法是在网页上开一个JavaScript
方法,在请求POST
的时候去调用JavaScript
这个方法,从而完成POST
请求。调用JavaScript
方法参考下面交互这一章节。
WKWebView
和JavaScript
交互,在WKUserContentController.h
这个头文件中- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
这个方法的注释中已经明确给出了交互办法。使用起来却是很是的简单。建立WKWebView
的时候添加交互对象,并让交互对象实现WKScriptMessageHandler
中的惟一的一个代理方法。具体的方式参考Demo中的使用。
// 添加交互对象 [config.userContentController addScriptMessageHandler:(id)self.ocjsHelper name:@"timefor"]; /** 此点后来更新,若是不移除交互对象,则致使交互对象内存常驻(2016.12.17) */ // VC销毁时,移除交互对象 [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"timefor"]; // 代理方法 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
JavaScript
调用Objective-C
的时候,使用window.webkit.messageHandlers.timefor.postMessage({code: '0001', functionName: 'getdevideId'}); Objective-C
自动对交互参数包装成了WKScriptMessage
对象,其属性body
则为传送过来的参数,name
为添加交互对象的时候设置的名字,以此名字能够过滤掉不属于本身的交互方法。其中body
能够为NSNumber, NSString, NSDate, NSArray, NSDictionary, and NSNull。
而Objective-C
在回调JavaScript
的时候,不能像我原来在 Objective-C与JavaScript交互的那些事这篇文章中写的那样,JavaScript
传过来一个匿名函数,Objective-C
这边直接调用一下就完事。WKWebView
没有办法传过来一个匿名函数,因此回调方式,要么执行一段JavaScript
代码,或者就是调用JavaScript
那边的一个全局函数。通常是采用后者,至于Web
端虽然说暴露了一个全局函数,一样能够把这一点代码处理的很优雅。Objective-C
传给JavaScript
的参数,能够为Number, String, and Object
。参考以下:
// 数字 NSString *js = [NSString stringWithFormat:@"globalCallback(%@)", number]; [self.webView evaluateJavaScript:js completionHandler:nil]; // 字符串 NSString *js = [NSString stringWithFormat:@"globalCallback(\'%@\')", string]; [self.webView evaluateJavaScript:js completionHandler:nil]; // 对象 NSString *js = [NSString stringWithFormat:@"globalCallback(%@)", @{@"name" : @"timefor"}]; [self.webView evaluateJavaScript:js completionHandler:nil]; // 带返回值的JS函数 [self.webView evaluateJavaScript:@"globalCallback()" completionHandler:^(id result, NSError * _Nullable error) { // 接受返回的参数,result中 }];
此文主要介绍了WKWebView
使用中的注意点,通常也都是经常使用的,还有缓存等一些不是太经常使用的就没有具体介绍。若是在其余方面遇到问题,也欢迎你私信我共同探讨进步。WKWebView
确实比UIWebView
有些地方好用很多,可是一些bug
至今也没解决,权限挑战是在iOS9
解决的,POST
请求则至今没有解决,而改变contentInset
致使的点击事件不许确,一样是没有解决。这些问题让开发者使用起来,有诸多不便啊。
此文的Demo地址:WKWebViewDemo 若是此文对你有所帮助,请给个star
吧。