iOS 8 WKWebView 知識點

首先看看這篇文章,寫得很好: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下的。

相關文章
相關標籤/搜索