首先看看這篇文章,寫得很好:http://nshipster.cn/wkwebkit/javascript
再推薦去看看 iOS_8_by_Tutorials 這本書裏的 WKWebView相關章節!html
我這裏說下本身的簡單體會:java
1.對比UIWebView ,網上說WKWebView的效率要高,到底高多少,不清楚。ios
2.WKWebView將javascript的注入,以及javascript傳回數據的方法標準化了。在UIWebView時代,執行javascript沒什麼問題,可是從javascript傳回數據就麻煩得多,大可能是經過拼寫url,調用shouldStartLoadWithRequest方法時傳入json數據,寫起來十分不規範。也有一些第三方庫實現的不錯,但畢竟不是原生的。使用WKWebView就能夠經過在js中調用webkit.messageHandlers 發送數據到在oc中的代理函數。詳見 iOS_8_by_Tutorials。web
在swift中插入函數接口的方法:json
class NotificationScriptMessageHandler: NSObject, WKScriptMessageHandler { func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage!) { println(message.body) } } let userContentController = WKUserContentController() let handler = NotificationScriptMessageHandler() userContentController.addScriptMessageHandler(handler, name: "handlerName")
下面是js中的方法,注意,能夠直接傳入js數組,會自動轉化爲swift可識別的數組!這一點很是好,不須要使用json本身轉換了。swift
function getRelatedArticles() { var related = []; var elements = document.getElementById("related").getElementsByTagName("a"); for (i = 0; i < elements.length; i++) { var a = elements[i]; related.push({href: a.href, title: a.title}); } window.webkit.messageHandlers.handlerName.postMessage({articles: related}); }
3.WKWebView能夠監聽到載入進度了。api
4.用新的代理函數數組
func webView(webView: WKWebView!, decidePolicyForNavigationAction navigationAction: WKNavigationAction!, decisionHandler: ((WKNavigationActionPolicy) -> Void)!)
替代了 UIWebView使用的安全
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
去決定一個url請求是否應該被執行。
5.ios9用WKWebView讀取本地文件時,須要用到一個特殊函數,否則沒有權限
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL NS_AVAILABLE(10_11, 9_0);
這個函數是ios9纔有的,若是你要支持ios8,請參考如下連接 http://stackoverflow.com/questions/24882834/wkwebview-not-loading-local-files-under-ios-8/28676439#28676439 簡單地說就是建立了一個特殊目錄。
6.若是使用WKWebView讀取本地文件,就涉及到一個獲取本地文件url的問題,好比有一個文件,它在main bundle中,路徑是
/Users/Rufus/Library/Developer/CoreSimulator/Devices/15F63516-5F19-4CE9-B709-AC7FC0F9E660/data/Containers/Bundle/Application/C5EDBEDF-54B1-4B7C-9EE5-216337584D2D/HtmlWrapper.app/1.png
爲了顯示它,咱們能夠須要建立一個request,而建立request須要url,那麼用下面方法中的哪個方法建立url呢?
let fileUrl = URL(fileURLWithPath: resourcePath) let normalUrl = URL(string: resourcePath)
答案是,第一個 let fileUrl = URL(fileURLWithPath: resourcePath)。
若是你使用了let normalUrl = URL(string: resourcePath) 生成一個url,並利用這個url生成request,再傳遞給webview,webview是讀不出任何東西的。
這是由於fileURLWithPath這個方法生成的是一個
file:///Users/Rufus/Library/Developer/CoreSimulator/Devices/15F63516-5F19-4CE9-B709-AC7FC0F9E660/data/Containers/Bundle/Application/C5EDBEDF-54B1-4B7C-9EE5-216337584D2D/HtmlWrapper.app/1.png
這種形式的url。這個file,就像http同樣,是一種協議,這個協議指的是從本地存儲中讀取資源,這個協議不須要本身架設什麼服務器,系統底層就會執行具體操做,返回文件內容。
而若是咱們使用URL(string: resourcePath),生成的就是一個
/Users/Rufus/Library/Developer/CoreSimulator/Devices/15F63516-5F19-4CE9-B709-AC7FC0F9E660/data/Containers/Bundle/Application/C5EDBEDF-54B1-4B7C-9EE5-216337584D2D/HtmlWrapper.app/1.png
這樣的Url,這個url沒有寫明協議,那麼系統會默認爲http協議,並可能補全缺乏的主機名,因此WKWebView最終可能使用的是
http://localhost/Users/Rufus/Library/Developer/CoreSimulator/Devices/15F63516-5F19-4CE9-B709-AC7FC0F9E660/data/Containers/Bundle/Application/C5EDBEDF-54B1-4B7C-9EE5-216337584D2D/HtmlWrapper.app/1.png
想用http協議,必須有服務器,然而咱們又沒有在ios設備上運行http服務器,固然沒法經過http協議讀取這個所謂路徑的任何東西了。
這裏須要注意的是:在UIWebView上,是可使用URL(string: resourcePath)讀取一個本地的文件的!應該是UIWebView內部有邏輯,可以自動識別file 協議的url,並按照file協議讀取對應文件。可是,這並非一個嚴謹的方法,在建立本地文件url時,就該使用 let fileUrl = URL(fileURLWithPath: resourcePath)這種形式。
7.
在用webview請求內容時,常常會遇到重定向問題,看一下重定向的相關知識:
http://blog.csdn.net/bluishglc/article/details/7953614
這裏簡單地總結下,最常遇到的重定向是服務器返回一個301或者302狀態的http response,並將新的地址加到這個response的header中,對應的key是Location。webview收到response後,查看是這個狀態,就會自動發起另外一個請求,跳轉到新地址。
8.對於ios8以上的版本,不該該使用UIWebView了,官方文檔已經明確指出:
Important Starting in iOS 8.0 and OS X 10.10, use WKWebView to add web content to your app. Do not use UIWebView or WebView.
9.對於WKWebView,主要有4個load方法,咱們分別看一看
open func load(_ request: URLRequest) -> WKNavigation? open func loadFileURL(_ URL: URL, allowingReadAccessTo readAccessURL: URL) -> WKNavigation? open func loadHTMLString(_ string: String, baseURL: URL?) -> WKNavigation? open func load(_ data: Data, mimeType MIMEType: String, characterEncodingName: String, baseURL: URL) -> WKNavigation?
先看看,open func loadHTMLString(_ string: String, baseURL: URL?) -> WKNavigation?
這個方法能夠經過baseURL的解釋以下:A URL that is used to resolve relative URLs within the document.
舉個例子,好比在html中,存在這樣的代碼
<script src="/javascripts/browser.min.js"></script>
那麼,這個/javascripts/browser.min.js就是相對路徑,relative url,單憑這個url是讀不到任何資源的,由於它不完整,解析這個html的模塊,都會負責把這個url補全,再去加載對應的資源。而wevView就能夠利用這裏的baseUrl進行補全。
說到baseUrl,再解釋一下baseURL。在stackoverflow上有以下答案:
-baseURL is a concept purely of NSURL/CFURL rather than URLs in general
就是說 baseURL並不存在真正的URL定義中,僅僅是cocoa 庫的一個寫法,再看看URL的定義
Uniform / Universal Resource Locator,常縮寫爲URL, 統一資源定位符的標準格式以下: 協議類型://服務器地址(必要時需加上端口號)/路徑/文件名
這裏的協議,除了經常使用的http,https,還有一些別的,好比file,ftp 類型。咱們把桌面上的圖片拖入遊覽器當中,遊覽器顯示了圖片,而且在地址欄上顯示了file:///Users/Rufus/Desktop/1.png,這個就是URL的一種,file協議省略了服務器名,因此出現了三個/,實際上是 file://指的是協議,後面指的是路徑。
能夠看到,根本沒有什麼baseUrl的說明,這個baseUrl,就是ios提供的便利方法,方便把html中常出現的相對路徑(relative url),轉化爲一個完整的url(absolute url)。
具體的做用和使用方法須要根據每一個api而定。
再看看這個open func loadFileURL(_ URL: URL, allowingReadAccessTo readAccessURL: URL) -> WKNavigation?
讀起來好像能夠給出訪問資源的權限?可是什麼地方的文件會有權限不讓訪問呢?我在ios10上作了如下測試:
print("home is",NSHomeDirectory()) // Do any additional setup after loading the view, typically from a nib. let wkWebView = WKWebView(frame:CGRect(x: 0, y: 400, width: 500, height: 500)) self.view.addSubview(wkWebView) let path = Bundle.main.path(forResource:"1", ofType: "png") let documentsPath = NSHomeDirectory()+"/Documents/1.png" let tmpPath = NSTemporaryDirectory()+"/1.png" let cachePath = NSHomeDirectory()+"/Library/Caches/1.png" let fileManager = FileManager.default; do{ try fileManager.copyItem(atPath: path!, toPath: cachePath) try fileManager.copyItem(atPath: path!, toPath: documentsPath) try fileManager.copyItem(atPath: path!, toPath: tmpPath) }catch{ print(error) } wkWebView.load(URLRequest(url:URL(fileURLWithPath: path!))) //wkWebView.load(URLRequest(url:URL(fileURLWithPath: documentsPath))) //wkWebView.load(URLRequest(url:URL(fileURLWithPath: tmpPath))) //wkWebView.load(URLRequest(url:URL(fileURLWithPath: cachePath)))
經常使用的4個路徑都測試了,均可以正確地讀取數據。是否是因爲這個文件是本地拷貝的因此才行呢?我決定再實驗一個從遠程下載的文件。
let urlSession = URLSession(configuration: URLSessionConfiguration.default) urlSession.downloadTask(with:URL(string:"https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png")!) { (url:URL?, response:URLResponse?, error:Error?) in print("url is ",url); let dstPath = NSHomeDirectory()+"/Documents/2.png" let dstPath2 = NSHomeDirectory()+"/Library/Caches/2.png" let dstUrl = URL(fileURLWithPath: dstPath2) do{ try fileManager.moveItem(at: url!, to: dstUrl) }catch{ print(error) } wkWebView.load(URLRequest(url:dstUrl)) }.resume()
結果是,也均可以正常讀取資源。
那麼這個 allowingReadAccessTo 方法到底在什麼條件下使用呢?
測試的html文件內容以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> aaaaa <img src="./1.png"> </body> </html>
這個html放在documents下面,1.png也是放在documents下面。
將這個html分別放在實驗1中的4個位置,讀取資源,這個資源的位置也須要分別放在4個位置,其實就是16種狀況。
我作了如下測試:
let documentDirPath = NSHomeDirectory()+"/Documents"
let documentUrl = URL(fileURLWithPath: documentDirPath)
生成Url時的baseUrl 和 webview load 時的baseUrl 沒什麼聯繫,後者才能把Html內容中相對路徑補全爲絕對路徑。
let fileUrl = URL(fileURLWithPath: "3.html", relativeTo: documentUrl)
let fileUrl2 = URL(fileURLWithPath:(documentDirPath+"/3.html"))
//wkWebView.loadFileURL(fileUrl, allowingReadAccessTo:documentUrl) //wrong
//wkWebView.loadFileURL(fileUrl2, allowingReadAccessTo:Bundle.main.resourceURL!) //wrong
//wkWebView.loadFileURL(fileUrl2, allowingReadAccessTo:documentUrl)
wkWebView.load(URLRequest(url:fileUrl)) //simulator :correct device:wrong
先看一下2個url的不一樣之處,
(lldb) po fileUrl.baseURL ▿ Optional<URL> ▿ some : file:///Users/Rufus/Library/Developer/CoreSimulator/Devices/C83D85F0-82E8-4608-B643-890A75362FEB/data/Containers/Data/Application/E56AF850-3CD2-4D76-88F3-8F37BA74A054/Documents/ (lldb) po fileUrl2.baseURL nil (lldb) po fileUrl.relativePath "3.html" (lldb) po fileUrl2.relativePath "/Users/Rufus/Library/Developer/CoreSimulator/Devices/C83D85F0-82E8-4608-B643-890A75362FEB/data/Containers/Data/Application/E56AF850-3CD2-4D76-88F3-8F37BA74A054/Documents/3.html" (lldb) po fileUrl.absoluteString "file:///Users/Rufus/Library/Developer/CoreSimulator/Devices/C83D85F0-82E8-4608-B643-890A75362FEB/data/Containers/Data/Application/E56AF850-3CD2-4D76-88F3-8F37BA74A054/Documents/3.html" (lldb) po fileUrl2.absoluteString "file:///Users/Rufus/Library/Developer/CoreSimulator/Devices/C83D85F0-82E8-4608-B643-890A75362FEB/data/Containers/Data/Application/E56AF850-3CD2-4D76-88F3-8F37BA74A054/Documents/3.html"
注意到,這2個url雖然 absoluteString 值徹底同樣,可是其餘的2個值徹底不一樣。其實,對於RFC來講,URL指的就是absoluteString,另外的2個值,都是爲了ios方便使用而設計的。
最後的這4種載入,前2個是錯誤的,後兩個是正確的,咱們看看那2個錯的有什麼問題。
//wkWebView.loadFileURL(fileUrl, allowingReadAccessTo:documentUrl) //wrong //wkWebView.loadFileURL(fileUrl2, allowingReadAccessTo:documentUrl) //correct
顯然,wkWebView.loadFileURL對於fileUrl是沒法讀取的,可是卻能讀取 fileUrl2這種最普通方法建立的Url!
再看這個
//wkWebView.loadFileURL(fileUrl, allowingReadAccessTo:documentUrl) //wrong wkWebView.load(URLRequest(url:fileUrl)) //simulator:correct device:wrong
wkWebView.load 方法倒是可使用fileUrl的!可是真機因爲訪問權限問題,load方法讀取不到任何本地資源,想讀取本地資源,就要使用loadFileURL方法
。
這樣看起來,日常最好仍是使用最基本的字符串建立Url!
再看下面的對比
//wkWebView.loadFileURL(fileUrl2, allowingReadAccessTo:Bundle.main.resourceURL!) //wrong //wkWebView.loadFileURL(fileUrl2, allowingReadAccessTo:documentUrl) //correct
這組對比,就能體現allowingReadAccessTo的做用了。第一個失敗了,就是由於在此次加載過程當中,僅僅賦予了Bundle.main.resourceURL中的權限,那麼放在documents目錄下的html天然沒法被webview載入了!
@discussion If readAccessURL references a single file, only that file may be loaded by WebKit.
If readAccessURL references a directory, files inside that file may be loaded by WebKit.
我以爲這是一個爲了安全而設計的方法:用這個方法加載一個不肯定是否安全的url,就能夠指定此次加載的訪問範圍,防止這個url訪問到別的資源。當懷疑一個url的安全性時,就應該使用這種加載方法!
須要特殊說明的是,這個allowingReadAccessTo參數在模擬器上不能限制html內部資源的訪問範圍,可是在真機上是能夠的!好比:
把allowingReadAccessTo設置到/Documents/Sub/範圍時,上層目錄中的圖片也能夠被順利讀取。
可是若是實在真機上,就不能讀取了!必須得到Documents的權限才行,由於1.png是直接放在documents下的。