前言:
Hybrid(混合)开发在现今的app开发中相当常见,本文的目的是从iOS端引导大家快速进入Hybrid开发。
正文:
首先,我们先来了解一下为什么混合开发是如此必要。无论是Android,还是iOS,如果你仅采用原生开发,那么你的每一次变动,都需要将应用打包后发给应用商店,等待审核成功后,用户安装了新版本才可以使用。这无疑是浪费了太多的时间。那么你自然会想到,把容易变化的页面放在html上,而将程序的固定模块用原生开发。也就是在客户端接入h5(即web前端,后文简称h5)的页面,完成我们所需的功能。
而由于h5的页面,不仅仅是展示功能,也包含用户的操作,因此还需要客户端与h5的互相调用,那么仅仅用webView显示,那是远远不够的。例如:h5页面需要上传相片,那么就需要客户端将相片传给h5。这里h5需要向客户端发送打开相机或相册的请求,客户端选中相片后,也需要向h5上传。那么,我们进入了今天的主题,Hybrid开发。
Hybrid开发的基础在于两点:
1:客户端对h5的调用
这里的关键方法只有一个
webView.evaluateJavaScript(javascriptString, completionHandler: nil)
其中webView是WKWebView
的实例.
WKWebView是苹果在iOS 8之后推出的框架WebKit中的浏览器控件, 其加载速度比UIWebView快了许多, 但内存占用率却下降很多, 也解决了加载网页时的内存泄露问题. 现在的项目大多数只需适配到iOS 8, 所以用WKWebView来替换项目中的UIWebView是很有必要的.
evaluateJavaScript
这个方法是向浏览器传递javaScript,其中参数javascriptString是JSON格式的字符串。
无论是向h5传递参数,还是调用方法,都要将执行的语句作为参数,并用WKWebView调用evaluateJavaScript
来传递给h5。
2.h5调客户端的调用
关键方法也是只有一个
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {}
需要遵循WKScriptMessageHandler协议, 在本方法中获取h5端传递的事件和参数,接下来再调用客户端的原生方法。
其中事件和参数都包含再message参数的body属性里
3.实例演示
这里先给大家推荐一个第三方库AXWebViewController
,该控制器自带WKWebView,并且对进度条等控件进行了封装,使用起来非常方便。只需要继承该控制器就可以了。
(1)首先引入AXWebViewController
,然后新建一个控制器WebViewController并继承AXWebViewController,作为主控制器。
import AXWebViewController
class WebViewController: AXWebViewController {}
(2)为主控制器添加extension,遵循AXWebViewControllerDelegate
//MARK: -AXWebViewControllerDelegate
extension WebViewController: AXWebViewControllerDelegate {
//开始加载
func webViewControllerDidStartLoad(_ webViewController: AXWebViewController) {}
//完成加载
func webViewControllerDidFinishLoad(_ webViewController: AXWebViewController) {
//DOM操作,对webView的基础设置
//1.禁止用户选中文本
webView.evaluateJavaScript("document.documetElement.style.webkitUserSelect='none';", completionHandler: nil)
//2.禁止按住目标的手势
webView.evaluateJavaScript("document.documentElement.style.webkitTouchCallout='none';", completionHandler: nil)
//3.向h5注入所需要的数据,如:app版本号,token等
self.invokeJavascript(method: "dataInit", data: ["token":token,"platform":"iOS","appVersion":sysManager.appVersion,"UUID":SysManager.main.uuid()])
}
//加载失败
func webViewController(_ webViewController: AXWebViewController, didFailLoadWithError error: Error) {}
}
以上3个代理方法,分别对应webView的开始加载,完成加载和加载失败3种状态。
其中开始加载和加载失败两个代理方法里,我们根据自己的需求来实现。如加载失败,显示失败对应的页面等。
那么重点来看完成加载这个方法。在本方法中:
1,2分别是都是直接调用WKWebView的evaluateJavaScript
方法,向h5注入语句达到1.禁止用户选中文本 2.禁止按住这个手势
3中所设置的是h5页面中的基础配置,其中调用了一个我们自己实现的方法,invokeJavascript,目的也是向h5传递javaScript。这里的方法dataInit和参数token,版本号等也都是和h5所约定好的。
func utimesInvokeJavascript(method:String, data:Any?) {
do {
let jsonData = try JSONSerialization.data(withJSONObject: data!, options: [])
let para = String(data: jsonData, encoding: .utf8)
let javaScriptString = "window.bridge.emit('"+method+"',"+para!+")"
webView.evaluateJavaScript(javaScriptString, completionHandler: { (_, error) in})
} catch {
print(error)
}
}
以上这个自定义的方法就是将参数和方法转化为JSON字符串,并传递给h5。其中的"window.bridge.emit('"+method+"',"+para!+")"也是和h5约定好的格式来接收。
之后每次想要向h5注入javaScript,也就是让h5响应客户端,我们都可以通过utimesInvokeJavascript
来调用evaluateJavaScript
.
(3)遵循WKScriptMessageHandler
协议
//messageHanlderName为和h5约定好的通信名称
webView.configuration.userContentController.add(self, name: messageHandlerName)
实现协议方法
//MARK: -WKScriptMessageHandler
extension WebViewController: WKScriptMessageHandler {
//负责接收h5对客户端原生方法的调用
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
let dictionary = message.body as! Dictionary<String,Any>
let method = dictionary["method"] as? String
let args = dictionary["args"] as? Dictionary<String,Any>
self.reflectMethod(method:method, args: args)
}
}
h5对客户端调用时,需要传递参数对象过来,就包含在message对象的body属性里。
这里面的method和args两个参数对应调用的方法和参数,也是h5和客户端所约定的方式
其中reflectMethod这个方法是自定义以响应h5对客户端的调用。
//响应javascript对原生方法的调用
func reflectMethod(method:String?, args:Dictionary<String,Any>?) {
if method == "goBack" {
perform(#selector(back), with: args, afterDelay: 0.1)
}
}
@objc func goBack() {
navigationController?.popViewController(animated: true)
}
例如我们定义了一个名为goBack的方法,那么h5只需按约定方式将goBack对应字符串放进method参数里再传递过来,我们就在识别后就可以响应此方法
(4)当h5页面加载出现异常可能会有以下情况
1.可能因为h5需要的参数未传递
//此方法属于AXWebViewControllerDelegate的代理方法
func webViewController(_ webViewController: AXWebViewController, didFailLoadWithError error: Error) {
//对错误进行处理,如显示加载失败等
}
2.可能因内存过高而导致webView进程的结束,如多次上传图片
//此方法属于WKNavigationDelegate,因AXWebViewController遵循WKNavigationDelegate,因此可直接重写
override func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
//可调用webView.reload()重新加载,或者返回上一页面
}
(5)当需要调用时,我们只需要实例化WebViewController
就好了
let webController = WebViewController(address: urlString) //urlString为html地址
navigationController?.pushViewController(vc, animated: true)
总结:
那么再来总结一下关键点:
1.创建控制器,继承于第三方控制器AXWebViewController
1.使用WKWebView,通过webView.evaluateJavaScript
(javaScriptString, completionHandler: nil)向h5注入javascript,使h5端响应客户端要求;
2.遵循WKScriptMessageHandler,实现代理方法func userContentController
(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage){},使客户端响应h5端要求
3.在func webViewControllerDidFinishLoad
(_ webViewController: AXWebViewController) {}在完成h5端需要的基本配置,如token,uuid等
4.javaScript的传递格式需要客户端和h5端共同约定