前言:
Hybrid(混合)開發(fā)在現(xiàn)今的app開發(fā)中相當(dāng)常見硕并,本文的目的是從iOS端引導(dǎo)大家快速進(jìn)入Hybrid開發(fā)法焰。
正文:
首先,我們先來了解一下為什么混合開發(fā)是如此必要倔毙。無論是Android埃仪,還是iOS,如果你僅采用原生開發(fā),那么你的每一次變動(dòng)陕赃,都需要將應(yīng)用打包后發(fā)給應(yīng)用商店卵蛉,等待審核成功后颁股,用戶安裝了新版本才可以使用。這無疑是浪費(fèi)了太多的時(shí)間傻丝。那么你自然會(huì)想到甘有,把容易變化的頁面放在html上,而將程序的固定模塊用原生開發(fā)葡缰。也就是在客戶端接入h5(即web前端亏掀,后文簡稱h5)的頁面,完成我們所需的功能泛释。
而由于h5的頁面滤愕,不僅僅是展示功能,也包含用戶的操作怜校,因此還需要客戶端與h5的互相調(diào)用该互,那么僅僅用webView顯示,那是遠(yuǎn)遠(yuǎn)不夠的韭畸。例如:h5頁面需要上傳相片宇智,那么就需要客戶端將相片傳給h5。這里h5需要向客戶端發(fā)送打開相機(jī)或相冊的請求胰丁,客戶端選中相片后随橘,也需要向h5上傳。那么锦庸,我們進(jìn)入了今天的主題机蔗,Hybrid開發(fā)。
Hybrid開發(fā)的基礎(chǔ)在于兩點(diǎn):
1:客戶端對h5的調(diào)用
這里的關(guān)鍵方法只有一個(gè)
webView.evaluateJavaScript(javascriptString, completionHandler: nil)
其中webView是WKWebView
的實(shí)例.
WKWebView是蘋果在iOS 8之后推出的框架WebKit中的瀏覽器控件, 其加載速度比UIWebView快了許多, 但內(nèi)存占用率卻下降很多, 也解決了加載網(wǎng)頁時(shí)的內(nèi)存泄露問題. 現(xiàn)在的項(xiàng)目大多數(shù)只需適配到iOS 8, 所以用WKWebView來替換項(xiàng)目中的UIWebView是很有必要的.
evaluateJavaScript
這個(gè)方法是向?yàn)g覽器傳遞javaScript,其中參數(shù)javascriptString是JSON格式的字符串甘萧。
無論是向h5傳遞參數(shù)萝嘁,還是調(diào)用方法,都要將執(zhí)行的語句作為參數(shù)扬卷,并用WKWebView調(diào)用evaluateJavaScript
來傳遞給h5牙言。
2.h5調(diào)客戶端的調(diào)用
關(guān)鍵方法也是只有一個(gè)
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {}
需要遵循WKScriptMessageHandler協(xié)議, 在本方法中獲取h5端傳遞的事件和參數(shù),接下來再調(diào)用客戶端的原生方法怪得。
其中事件和參數(shù)都包含再message參數(shù)的body屬性里
3.實(shí)例演示
這里先給大家推薦一個(gè)第三方庫AXWebViewController
,該控制器自帶WKWebView,并且對進(jìn)度條等控件進(jìn)行了封裝咱枉,使用起來非常方便。只需要繼承該控制器就可以了徒恋。
(1)首先引入AXWebViewController
,然后新建一個(gè)控制器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的基礎(chǔ)設(shè)置
//1.禁止用戶選中文本
webView.evaluateJavaScript("document.documetElement.style.webkitUserSelect='none';", completionHandler: nil)
//2.禁止按住目標(biāo)的手勢
webView.evaluateJavaScript("document.documentElement.style.webkitTouchCallout='none';", completionHandler: nil)
//3.向h5注入所需要的數(shù)據(jù)入挣,如: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個(gè)代理方法,分別對應(yīng)webView的開始加載径筏,完成加載和加載失敗3種狀態(tài)葛假。
其中開始加載和加載失敗兩個(gè)代理方法里障陶,我們根據(jù)自己的需求來實(shí)現(xiàn)。如加載失敗桐款,顯示失敗對應(yīng)的頁面等咸这。
那么重點(diǎn)來看完成加載這個(gè)方法。在本方法中:
1魔眨,2分別是都是直接調(diào)用WKWebView的evaluateJavaScript
方法媳维,向h5注入語句達(dá)到1.禁止用戶選中文本 2.禁止按住這個(gè)手勢
3中所設(shè)置的是h5頁面中的基礎(chǔ)配置,其中調(diào)用了一個(gè)我們自己實(shí)現(xiàn)的方法遏暴,invokeJavascript,目的也是向h5傳遞javaScript侄刽。這里的方法dataInit和參數(shù)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)
}
}
以上這個(gè)自定義的方法就是將參數(shù)和方法轉(zhuǎn)化為JSON字符串朋凉,并傳遞給h5州丹。其中的"window.bridge.emit('"+method+"',"+para!+")"也是和h5約定好的格式來接收。
之后每次想要向h5注入javaScript,也就是讓h5響應(yīng)客戶端杂彭,我們都可以通過utimesInvokeJavascript
來調(diào)用evaluateJavaScript
.
(3)遵循WKScriptMessageHandler
協(xié)議
//messageHanlderName為和h5約定好的通信名稱
webView.configuration.userContentController.add(self, name: messageHandlerName)
實(shí)現(xiàn)協(xié)議方法
//MARK: -WKScriptMessageHandler
extension WebViewController: WKScriptMessageHandler {
//負(fù)責(zé)接收h5對客戶端原生方法的調(diào)用
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對客戶端調(diào)用時(shí)墓毒,需要傳遞參數(shù)對象過來,就包含在message對象的body屬性里亲怠。
這里面的method和args兩個(gè)參數(shù)對應(yīng)調(diào)用的方法和參數(shù)所计,也是h5和客戶端所約定的方式
其中reflectMethod這個(gè)方法是自定義以響應(yīng)h5對客戶端的調(diào)用。
//響應(yīng)javascript對原生方法的調(diào)用
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)
}
例如我們定義了一個(gè)名為goBack的方法团秽,那么h5只需按約定方式將goBack對應(yīng)字符串放進(jìn)method參數(shù)里再傳遞過來主胧,我們就在識別后就可以響應(yīng)此方法
(4)當(dāng)h5頁面加載出現(xiàn)異常可能會(huì)有以下情況
1.可能因?yàn)閔5需要的參數(shù)未傳遞
//此方法屬于AXWebViewControllerDelegate的代理方法
func webViewController(_ webViewController: AXWebViewController, didFailLoadWithError error: Error) {
//對錯(cuò)誤進(jìn)行處理习勤,如顯示加載失敗等
}
2.可能因內(nèi)存過高而導(dǎo)致webView進(jìn)程的結(jié)束踪栋,如多次上傳圖片
//此方法屬于WKNavigationDelegate,因AXWebViewController遵循WKNavigationDelegate图毕,因此可直接重寫
override func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
//可調(diào)用webView.reload()重新加載夷都,或者返回上一頁面
}
(5)當(dāng)需要調(diào)用時(shí),我們只需要實(shí)例化WebViewController
就好了
let webController = WebViewController(address: urlString) //urlString為html地址
navigationController?.pushViewController(vc, animated: true)
總結(jié):
那么再來總結(jié)一下關(guān)鍵點(diǎn):
1.創(chuàng)建控制器吴旋,繼承于第三方控制器AXWebViewController
1.使用WKWebView,通過webView.evaluateJavaScript
(javaScriptString, completionHandler: nil)向h5注入javascript损肛,使h5端響應(yīng)客戶端要求;
2.遵循WKScriptMessageHandler,實(shí)現(xiàn)代理方法func userContentController
(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage){},使客戶端響應(yīng)h5端要求
3.在func webViewControllerDidFinishLoad
(_ webViewController: AXWebViewController) {}在完成h5端需要的基本配置荣瑟,如token,uuid等
4.javaScript的傳遞格式需要客戶端和h5端共同約定