(原文:A Look at the WebKit Framework – Part 1 作者:Joyce Echessa 譯者:ibenjamin )
如果你曾經在你的App中使用UIWebView加載網頁內容的話,你應該體會到了它的諸多不盡人意之處。UIWebView是基於移動版的Safari的,所以它的性能表現十分有限。特別是在對幾乎每個Web應用都會使用的JavaScript,表現的尤為糟糕。
但是,所有的這一切都在iOS 8引入了一個新的框架——WebKit,之後變得好起來了。在WebKit框架中,有WKWebView可以替換UIKit的UIWebView和AppKit的WebView,而且提供了在兩個平台可以一致使用的接口。
WebKit框架使得開發者可以在原生App中使用Nitro來提高網頁的性能和表現,Nitro就是Safari的JavaScript引擎。
WKWebView保證在滑動時保持60的幀率,同時具有KVO,內建手勢,以及在App和網頁之間的原生交流方式。
橫跨2篇文章,我們即將建立2個App來探索WebKit的功能(特別是WKWebView)。在第一個App當中,我們將建立一個和Safari 功能相似的浏覽器。在第二篇文章中,我們會深入到Webkit中去,探索更強大的功能:注入JavaScript到網頁以改變內容和獲取數據。
打開Xcode,創建一個新的工程。選擇Single View Application,取名叫Browser,選擇Swift為開發語言,Devices選擇Universal。
在ViewController.swift中導入WebKit框架。
import WebKit
將下面的變量加入到類中
var webView: WKWebView
將下面的方法加入到類中。它將會初始化webview並設置其frame為0.稍後我們會使用自動布局(auto layout)來給webview添加約束,這樣這個webview就能在任何蘋果設備和任何方向上正常工作了。
required init(coder aDecoder: NSCoder) { self.webView = WKWebView(frame: CGRectZero) super.init(coder: aDecoder) }
在viewDidLoad()底部,添加如下語句。這樣這個webView就被添加到主視圖了。
view.addSubView(webView)
接下來,在viewDidLoad()方法底部添加如下約束
webView.setTranslatesAutoresizingMaskIntoConstraints(false) let height = NSLayoutConstraint(item: webView, attribute: .Height, relatedBy: .Equal, toItem: view, attribute: .Height, multiplier: 1, constant: 0) let width = NSLayoutConstraint(item: webView, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0) view.addConstraints([height, width])
在此處,我們首先禁止了自動約束。然後我們對webview的寬和高添加了約束。這樣webview就會和它的superview擁有一樣的寬和高了。
在程序啟動時,我們將會打開一個默認頁。然後我們添加一個textfield控件,這樣用戶就能輸入自己想浏覽的地址了。在viewDidLoad()底部添加如下代碼
let url = NSURL(string:"http://www.appcoda.com") let request = NSURLRequest(URL:url!) webView.loadRequest(request)
運行程序。它將會加載Appcoda的主頁。注意當你滑動頁面的時候在導航欄下面也能看到模糊的網頁,我們將禁止這樣。首先打開 Main.storyboard選擇網頁顯示的ViewController。在屬性檢查(也就是右邊的第四欄中)將Extend Edges中得Under Top Bars去掉勾選。再次運行程序,我們就會發現導航欄已經沒有了毛玻璃效果,而且我們也不能看見它下面的網頁內容了。
接下來讓我們添加給用戶輸入URL的控件。
在storyboard文件中,給向導航欄中拖放一個view。在屬性檢查(右邊第四欄)中,設置其背景色為透明(clear color)。因為我們無法給導航欄中的view添加約束,我們將會以代碼的形式調整其大小。
@IBoutlet weak var barView: UIView!
在viewDidLoad()的super.viewDidLoad()之後添加如下代碼
barView.frame = CGRect(x:0, y: 0, width: view.frame.width, height: 30)
這段代碼設置了barView的大小。
將下面的方法添加到類
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { barView.frame = CGRect(x:0, y: 0, width: size.width, height: 30) }
這段代碼將在設備方向改變時重新設置barView的大小。
接下來運行程序,你可以看見在導航欄中展開的view,如果更改設備方向,這個view也會跟著改變大小。
接下來,拖放一個textfield控件到這個view裡面。然後點擊畫布右下方的Pin按鈕(第二個)。將其top,bottom,right,left的距離設置為0,如下圖所示。
然後依次點擊,Editor-》Resolve Auto Layout Issues-》Selected View-》Update Frame,解決警告。
然後創建一個outlet。取名urlField。你應會看到如下代碼。
@IBOutlet weak var urlField: UITextField!
我們希望viewcontroller能成為UITextFieldDelegate的代理,在storyboard的Document Outline中,按下control然後將textfield拖放到viewcontroller,然後在彈出視圖選擇delegate。
選擇textfield控件,在屬性檢查(右邊第四欄)設置如下屬性。
Clear Button:Appears while editing
Correction: NO
Keyboard Type:URL
Return Key:Go
在類聲明部分添加實現UITextFieldDelegate代碼。
class ViewController: UIViewController, UITextFieldDelegate
接下來添加如下UITextFieldDelegate代碼
func textFieldShouldReturn(textField: UITextField) -> Bool { urlField.resignFirstResponder() webView.loadRequest(NSURLRequest(URL: NSURL(string: urlField.text)!)) return false }
以上代碼會隱藏鍵盤,然後加載用戶輸入的url。嘗試輸入一個url,我們發現。我們必須輸入一個完整的url,比如:http://google.com。對我們的用戶這有一點麻煩,我們可以檢查用戶的輸入,然後給用戶輸入的url在必要時添加‘http://’前綴。這裡我們不會詳述了。
現在我們的浏覽器已經能工作了,但是它還缺少了一些浏覽器應有的功能。載入進度提示,前進和後退,刷新按鈕,等等。
通過KVO(Key Value Observing),我們可以監聽WKWebView的載入進度、網頁標題和url屬性。你可以使用這些來更新你的UI。
首先,讓我們添加後退、前進及刷新按鈕。
在storyboard中,選擇View Controller,點擊屬性檢查(Attributes Inspector),在Simulated Metrics一欄,將BottomBar選擇為None。
拖放一個Toolbar到view得底部。添加其left,right和bottom距離為0,並確保Constrans to margins一欄沒有被勾選。
在viewDidLoad()方法中調整webView的高度,以顯示toolbar。
let height = NSLayoutConstraint(item: webView, attribute: .Height, relatedBy: .Equal, toItem: view, attribute: .Height, multiplier: 1, constant: -44)
移除toolbar中得button,然後按順序添加以下控件:Bar Button Item、Fixed Space Bar Button Item、Bar Button Item,Flexible Space Bar Button Item和Bar Button Item。toolbar應該是這個樣子。
編輯bar button成下面的樣子。這些按鈕將成為我們的前進後退及刷新按鈕。在一個真實的App當中,為這些按鈕放上圖標將是更好的選擇,但是為了簡便,我們使用文本。toolbar接下來應該是這個樣子。
為每一個bar button創建一個outlet。取名為backButton,forwardButton和reloadButton。你應該會得到如下代碼
@IBOutlet weak var backButton: UIBarButtonItem! @IBOutlet weak var forwardButton: UIBarButtonItem! @IBOutlet weak var reloadButton: UIBarButtonItem!
然後為每個按鈕依次分別創建back,forward和reload方法。將每個action的Type更改為UIBarButtonItem。你應該會得到如下代碼。
@IBAction func back(sender: UIBarButtonItem) { } @IBAction func forward(sender: UIBarButtonItem) { } @IBAction func reload(sender: UIBarButtonItem) { }
在viewDidLoad()的底部添加如下代碼。我們不希望後退和前進按鈕在App被啟動時就可點擊。
backButton.enabled = false forwardButton.enabled = false
在viewDidLoad()的約束條件添加代碼之後,創建並載入一個任務代碼之前,添加如下代碼。這句代碼使得這個類成為了loading屬性的監聽者。
webView.addObserver(self, forKeyPath: "loading", options: .New, context: nil)
添加下面的方法到類。這個方法將會在可監聽的屬性變化時執行。後退和前進按鈕將根據當前webview的狀態來決定是否可被點擊。
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer) { if (keyPath == "loading") { backButton.enabled = webView.canGoBack forwardButton.enabled = webView.canGoForward } }
修改back(),forward()和reload()方法。
@IBAction func back(sender: UIBarButtonItem) { webView.goBack() } @IBAction func forward(sender: UIBarButtonItem) { webView.goForward() } @IBAction func reload(sender: UIBarButtonItem) { let request = NSURLRequest(URL:webView.URL!) webView.loadRequest(request) }
運行並測試這些按鈕。後退和前進按鈕開始時應該不可點擊。當你浏覽一個頁面時,後退按鈕應該可被點擊。當你後退時,前進按鈕應該可被點擊。點擊R按鈕,會重新加載頁面。
我們不能保證用戶總是輸入正確地url,我們需要寫代碼來獲取這一錯誤並提示用戶。
首先修改類聲明,如下所示:
class ViewController: UIViewController, UITextFieldDelegate, WKNavigationDelegate
WKWebView有一個屬性navigationDelegate,接受一個實現WKNavigationDelegate協議的對象。這個協議提供了多種方法來處理導航事件,包括載入錯誤。
在init()底部添加如下代碼。通過下面的代碼,我們將這個類設置成了webview的navigation代理。
self.webView.navigationDelegate = self
接下來添加如下方法。這個代理方法將會在有錯誤發生時被調用。
func webView(webView: WKWebView!, didFailProvisionalNavigation navigation: WKNavigation!, withError error: NSError!) { let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "Ok", style: .Default, handler: nil)) presentViewController(alert, animated: true, completion: nil) }
運行程序,輸入一個錯誤的url測試一下。
最後,我們將會添加一個進度顯示。
在storyboard文件中,在導航欄下方添加一個progress view。設置其top,right和left如下所示。
為progress view創建一個outlet。你應該會得到下面的代碼。
@IBOutlet weak var progressView: UIProgressView!
在ViewController中,替換西面的代碼
view.addSubview(webView)
為
view.insertSubview(webView, belowSubview: progressView)
在viewDidLoad()中創建和載入一個url任務之前添加下面的代碼,調用webview的addObserver方法。
webView.addObserver(self, forKeyPath: "estimatedProgress", options: .New, context: nil)
在observeValueForKeyPath()方法中的其他if語句之後添加如下代碼。
if (keyPath == "estimatedProgress") { progressView.hidden = webView.estimatedProgress == 1 progressView.setProgress(Float(webView.estimatedProgress), animated: true) }
這段代碼將會更新progressview的進度,如果加載完畢會隱藏progressview。
在類中添加如下代碼。這是一個WKNavigationDelegate的代理方法,將會在頁面載入完畢後執行。當一個任務完成後,我們使用它來重置progress view的進度。
func webView(webView: WKWebView!, didFinishNavigation navigation: WKNavigation!) { progressView.setProgress(0.0, animated: false) }
運行程序,當網頁加載時,你將會看到一個藍色的進度條。
我們已經了解了WebKit的基礎部分。我們看到如何添加一些和Safari相似的功能,如載入url,浏覽歷史,檢測錯誤,展示進度。在這個教程的第二部分,我們會深入了解如何將一個JavaScript注入網頁,來構建一個更強大的程序。
你可以通過這個鏈接來下載這個教程的實例代碼。
(本文為CocoaChina組織翻譯,本譯文權利歸譯者所有,未經允許禁止轉載。)