好像是macOS10.10之后,以及iOS8之后,新出現的WKWebview組件就迅速的替代了Webview及UIWebView。后者的確存在一些無法解決的bug,諸如架構導致的速度緩慢和內存泄漏。
但無法避免的問題總是有的,比如有些客戶端軟件,仍然要求兼容老版本的系統,這時候,很不想使用,但也不得不仍然把Webview塞到自己的代碼中。
互聯網是個喜新厭舊的圈子,網上搜索,幾乎只有兩類。一是WKWebview的文檔,二是iOS類的文檔。想要的macOS下面Webview的資料緲如黃鶴。
經過部分只言片語的資料指導和大量的實驗,終於完成了工作。所以決定來燒燒冷灶,寫出來記錄一下。
1.添加Webview
最簡單添加webview的方法就是直接在Interface Builder中把Webview拖入到窗口並且用鼠標拖動到指定位置和指定大小,隨后在程序中加上對應的變量:
@IBOutlet weak var webView: WebView!
如果必須動態程序實現,可以使用window.contentView?.addSubview(webView)把webview控件插入到界面中。
2.載入網頁
- 可以直接導向到某個網頁,也可以先在本地啟動一個靜態頁面文件,后續一些工作可以在本地靜態網頁中用js處理。這種方法是比較多用的,因為程序啟動速度會感覺快的很多。
let path = Bundle.main.path(forResource: "somepage", ofType: "html")
let url = NSURL.fileURL(withPath: path!)
let request = URLRequest(url: url);
self.webView.mainFrame.load(request);
- 把somepage.html添加到項目,並在項目設置中Build Phases->Copy Bundle Resources中添加上文件somepage.html,這樣最后生成app文件的時候,somepage.html文件才會被打包到其中。
- 如果建立的項目使用沙箱(sandbox)模式,現在的應用,如果想上App Store,一般是強制要求使用沙箱的,需要在系統設置的Capabilities中允許incoming network/output networking。否則本地網頁沒問題,之后的任何網站都無法訪問。
- 新版本的macOS及iOS都強制必須使用https網頁訪問,如果需要支持老的http網頁,還需要在Info.plist中增加一行:App Transport Security Settings,類型為字典項,其中增加一項:Allow Arbitrary Loads,值為YES。
完成以上4項,網頁已經可以訪問了。
3.從swift調用js
假定在網頁中有如下內容:
<script>
function callFromSwift(msg){
document.getElementById('msgbox').innerHTML=msg;
return("msg return from js");
}
</script>
<div id='msgbox'></div>
其中定義了一個函數callFromSwift,當被調用的時候,在下面預定義的div中顯示傳入的字符串,並且返回一個字符串“msg return from js”。
在swift中調用網頁中的callFromSwift函數並獲取其返回值可以這樣做:
let s=webView.windowScriptObject.evaluateWebScript("callFromSwift('Hello, JavaScript')")
NSLog(s as! String) //s是js函數的返回結果,可以是多種類型,本例要求是string
4.從js調用swift
前面的3部分都比較容易,跟WKWebview也大同小異。從JS到swift的調用要復雜的多了。
首先在初始化的時候,要加上一句:
webView!.frameLoadDelegate=self;
對應的,要在類聲明的位置加上一個繼承:WebFrameLoadDelegate,隨后加入代碼:
//為js對象聲明一個接口
func webView(_ webView: WebView!, didClearWindowObject windowObject: WebScriptObject!, for frame: WebFrame!) {
self.webView.windowScriptObject.setValue(self, forKey: "swiftHost")
}
//這個是基本框架,聲明了本類中有兩個函數會開放給js對象,並供其調用
//這里示例了兩個,一個是callFromJS1,另一個是quit
//注意swift中的函數名跟js中的函數名可以不一樣,
//#selector中指明的是swift中聲明的函數名,因為selector是object-c中的機制,
//所以后面在聲明真正函數的時候,前面必須加@objc的標志
//在后面return "xxx"的部分,返回的字符串js中會使用的名字,
//本例中,swift中函數名跟js中函數名使用了相同的名字,我認為這是好習慣
override class func webScriptName(for aSelector: Selector) -> String?
{
//NSLog("%@",aSelector.description)
if aSelector == #selector(callFromJS1)
{
return "callFromJS1"
}
else
if aSelector == #selector(quit)
{
return "quit"
}
else
{
return nil
}
}
//這個函數顧名思義,應當是不允許在js中調用的,對所有的來值都返回false表示全部允許調用
override class func isSelectorExcluded(fromWebScript aSelector: Selector) -> Bool
{
//NSLog("%@",aSelector.description)
return false
}
//具體的函數
@objc
func callFromJS1(message:String)
{
NSLog(message)
}
@objc
func quit()
{
NSLog("call for quit")
NSApp.terminate(self);
}
前三個函數是基本的框架,其中第二個麻煩一些,隨后實際上工作的函數沒有什么特別。
接着來看看js的部分:
<a href='javascript:testCallSwift();'>testCallSwift</a><p>
<a href='javascript:needQuit();'>Quit</a><p>
<script>
function testCallSwift(){
//注意調用方式,window是js的對象
//swiftHost是swift的接口
//其后則是聲明的swift函數
window.swiftHost.callFromJS1("hello swift");
}
function needQuit(){
window.swiftHost.quit();
}
</script>
5.截獲webview每一次訪問
跟上面類似,要再增加一個代理:
//初始化的時候增加:
webView!.policyDelegate=self;
並且聲明類的時候多一個繼承:WebPolicyDelegate。隨后代碼中可以實現一個接口:
func webView(_ webView: WebView!,
decidePolicyForNavigationAction actionInformation: [AnyHashable : Any]!,
request: URLRequest!,
frame: WebFrame!,
decisionListener listener: WebPolicyDecisionListener!) {
NSLog("nav to %@",request.url!.absoluteString) //這里是將要轉向的網址
listener.use() //允許訪問這個網址
//listener.ignore() //不允許訪問這個網址則調用這個
}
也有些程序中為了簡化從js調用swift的工作量,會用鏈接的方式,在鏈接地址中傳入一些指令,就可以用這個函數截獲網址並且處理,被處理的網址通常使用listener.ignore()來禁止本次瀏覽器轉向,免得影響當前頁面。
6.響應js中的警告窗
通常的webview都是不允許js中的alert警告窗的,一方面是為了應用程序整體的效果;另一方面,webview作為一個空間,自己沒有UI的控制權,所以類似的工作,是要有應用程序自己實現警告框窗口的。實現警告窗依然要給類增加一個集成WebUIDelegate,並在初始化中增加:
webView!.uiDelegate=self;
//隨后可以實現一個接口:
func webView(_ sender: WebView!,
runJavaScriptAlertPanelWithMessage message: String!,
initiatedBy frame: WebFrame!){
NSLog("msg of alert: %@",message)
}
如果不滿足於只是得到警告消息,要自己在這個函數中使用cocoa的警告窗來顯示相關的信息。
7.其它
還可以實現從js中訪問swift中的變量功能。使用isKeyExcludedFromWebScript和webScriptNameForKey函數,我用得少,如果需要,參考上面定義函數的方法,查一查官方文檔自己來試試吧。
