首先看看這篇文章,寫得很好:http://nshipster.cn/wkwebkit/
再推薦去看看 iOS_8_by_Tutorials 這本書里的 WKWebView相關章節!
我這里說下自己的簡單體會:
1.對比UIWebView ,網上說WKWebView的效率要高,到底高多少,不清楚。
2.WKWebView將javascript的注入,以及javascript傳回數據的方法標准化了。在UIWebView時代,執行javascript沒什么問題,但是從javascript傳回數據就麻煩得多,大多是通過拼寫url,調用shouldStartLoadWithRequest方法時傳入json數據,寫起來十分不規范。也有一些第三方庫實現的不錯,但畢竟不是原生的。使用WKWebView就可以通過在js中調用webkit.messageHandlers 發送數據到在oc中的代理函數。詳見 iOS_8_by_Tutorials。
在swift中插入函數接口的方法:
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自己轉換了。
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可以監聽到載入進度了。
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下的。
