Project4:用WKWebView做一個簡易瀏覽器

概述

摘要:嵌入Web Kit,學習委托,KVO,類和UIToolbar。

概念:loadView(),WKWebView,委托,類和結構體,NSURLRequest,UIToolbar,UIProgressView,鍵-值觀察。

1.設置

2.用WKWebView創建一個簡易瀏覽器

3.選擇一個網站:UIAlertController

4.監控頁面載入:UIToolbar和UIProgressView

5.最后的重構

6.總結


設置

這個項目要通過寫一個瀏覽器app來告訴你關于UIBarButtonItem,UIAlertController和NSURL的知識。這又是個簡單的項目,但學習就是一邊挑戰新事物一邊回顧舊知識的過程。

為了讓這個過程更加美好,我會用這個機會來告訴你很多新東西:WKWebView(Apple的超級網絡插件),UIToolbar(放UIBarButtonItem的工具欄組件),UIProgressView,委托,類和結構體,關鍵值觀察,還有怎么在代碼中創建你自己的視圖。最后,這是最后一個簡單項目。

開始吧。用Single View Application template創建一個新Xcode項目,命名為project4,選擇iPhone和swift,然后保存到你的桌面。打開Main.storyboard,選擇視圖控制器,然后選擇Editor>Embed In>Navigation Controller——這樣我們的故事板就完成了。


用WKWebView創建一個簡易瀏覽器

在項目1和2中,我們用IB完成了大量的布局工作,但這里我們的布局十分簡單,所以我們差不多可以用代碼完成所有的任務。之前我們都是在視圖中添加按鈕和圖像,但這里網絡視圖將要占據視圖控制器的整個頁面空間,自然而然它就是主視圖。

到目前為止,布局載入之后,我們都是用方法viewDidLoad()來設定我們的視圖。這次我們需要重寫視圖的實際載入過程——我們不要故事板上的空白,我們要自己的代碼。它依舊會在導航控制器中,但其它的由我們來決定。

打開ViewController.swift,在viewDidLoad()之前加上以下代碼:

override func loadView() {

? ? ? webView = WKWebView()

? ? ? webView.navigationDelegate = self

? ? ? view = webView

}

并不是非得把loadView()放在viewDidView()前面——你可以把它放在class ViewController: UIViewController { .. }兩個大括號之間的任何位置。但我建議你按一定順序來放置你的方法,因為loadView()在viewDidLoad()之前被調用,所以它應該被寫在前面。

不管怎么說,我們只關心三件事,因為現在你需要理解為什么我們要用關鍵字override。(提示:因為有從故事板載入布局的默認方法的存在!)第一步,我們創建了一個Apple的WKWebView網絡瀏覽器組件的新實例而且把它賦值給一個變量webView。第三步,我們讓網絡視圖變成我們的視圖。

是的,我漏掉了第二步,這是因為這里有一個新的概念:委托。委托是一種編程模式——在iOS中被大面積使用,因為一些很好的理由:很好懂,很好用,而且特別靈活。

委托是指A事物在B事物的方式行動,以A自己的名義來高效地回答問題和回應事件的發生。在我們的例子中,我們使用的是WKWebView:Apple的強大、靈活和高效的網絡渲染。但即便聰明如WKWebView,它也不知道我們的app想要如何表現,因為它現在是我們自定義的代碼。

委托辦法很巧妙:我們可以告訴WKWebView當有趣的事情發生時通知我們。在這里,我們把網絡視圖的navigationDelegate屬性設置成self,表示“當任何網頁導航發生時,請告訴我。”

當你做這件事時,有兩件事會發生:

1.你必須遵從協議。這是在用一種神奇的方式表達“如果你告訴我你可以接受我的委托,這里是你得實現的一些方法。”在navigationDelegate的情況中,所有的方法都是可選項,表示我們并不需要實現所有的方法。

2.任何你實現的方法都可以控制WKWebView的行為,任何你沒有實現的方法都會使用WKWebView的默認行為。

在我們深入之前,你可能注意到你的代碼實際并不能編譯。有三個原因,可以秒修復。

理由1:Swift不知道什么是WKWebView。你也發現,它不是以UI開頭的,所以不是UIKit的部分。所以我們需要引進一個新的架構,這樣我們就可以用了。“WKWebView”中的“WK”代表WebKit,所以回到文件頂部把它調整為:

import UIKit

import WebKit

理由2:我們沒有定義一個webView的屬性,但我們已經給它賦值了。可以在loadView()方法前面加上它的定義來修復:

var webView: WKWebView!

這定義了一個隱式解析可選的WKWebView實例叫webView。我會再解釋一遍這樣我們就會清楚:在WKWebView的結尾處的“!”是必要的因為這個屬性在被設置之前是nil。

理由3:當你設置任何委托,你需要遵從協議。是的,所有的navigationDelegate協議方法都是可選的,但是Swift并不清楚。它所知道的是我們承諾這個委托對于網絡視圖來說是合適的,但還沒有履行協議。

修復方法很簡單,但我想插入點其他東西,因為這個時間很合適。首先是修復:找到這行:

class ViewController: UIViewController {

把它改寫成:

class ViewController: UIViewController, WKNavigationDelegate {

就好了。我想聊的是class。因為我已經在使用一些詞比如“數據類型”、“組件”和“實例”等等,但卻不是非常的清楚——而且我感保證很多開發者完全就只是把它們看成是一種結果。你好,討厭鬼!

Swift中有兩種復雜的數據類型:結構體和類。它們很像,實際上就只有兩個要緊的區別。

第一個是類可以被繼承。在項目1里面我們就聊過了。在子類中你可以使用任何一種父類中定義好了的東西,而且你還可以在頂部加上你自己的定制內容。

第二個不同是當你把一個結構體傳入方法時,你傳入的是一個副本。也就是說,方法對結構體的影響僅限于方法內部。另一方面,當你把一個類的實例傳入到方法中時,傳入的是引用,即方法中操作的對象就是方法外的對象,所有的修改都會被保存下來。

就誰是誰而言:Int,Double,Float,String和Array都是結構體,UIViewController和任何UIView都是類。實際操作中,這意味著不論何時你把一個數組傳入到一個方法中,它就會被復制一份。粗看十分低效,特別是數組的數據量十分巨大的時候,但不用擔心:Swift會利用寫時拷貝(copy on write)盡可能避免任何表現打折。

回到代碼:所有的這些都很重要,因為我希望你理解這行代碼到底在說什么:

class ViewController: UIViewController, WKNavigationDelegate {

如你所見,代碼以class開頭,表明我們是在定義一個新類。接下來的大括號內的所有內容都屬于這個新類。ViewController是類的名字,不是什么特別牛叉的名字。

接下來的東西很有趣:是個冒號,然后是UIViewController和一個逗號,還有WKNavigationDelegate。這一部分叫做類型繼承從句,它真正的意思是告訴我們新類ViewController是由什么構成的:它繼承自UIViewController,履行WKNavigationDelegate協議。

順序很重要:第一個是父類,然后是所有需要履行的協議,都用逗號隔開。我們是說這里我們只遵循一個協議,但你可以添加你所需要的任何協議,無論多少。

所以,這行代碼的意思是“創建一個叫ViewController的UIViewController的子類,然后告訴編譯器我們可以安全使用WKNavigationDelegate協議。”

程序差不多可以用了,運行之前讓我們再加三行。請把下面的代碼加入到viewDidLoad()中,就在super調用后面:

let url = NSURL(string: "https://www.hackingwithswift.com")!

webView.loadRequest(NSURLRequest(URL: url))

webView.allowsBackForwardNavigationGestures = true

第一行創建了一個新NSURL,跟前一個項目一樣。這里我用了hackingwithswift.com作為示例,你可以改成其他你喜歡的。警告:你得確保你用https://來上你的網,因為iOS9不喜歡app不加安全措施地發送和接收數據。如果你想改寫什么,看下iOS9的App數據傳輸安全。

第二行做了兩件事:根據NSURL創建了一個新NSURLRequest對象,然后把它交給我們的網絡視圖去載入。

現在Apple系統可能會覺得它有點無厘頭,但WKWebView不會從像www.hackingwithswift.com這樣的字符串載入網站,甚至不會從一個NSURL中載入。你得先把字符串變成NSURL,然后再把它放進NSURLRequest中,這樣WKWebView才會載入。幸好不是太困難!

警告:你的URL必須是完整而且有效的,為了讓這個進程可以工作。也就是說,https://也是必要的。

第三行激活了網絡視圖中的一個屬性,這樣用戶就可以通過從網絡瀏覽器屏幕中的左邊沿掃到右邊沿來返回之前的頁面。這是個Safari中常用的功能,所以最好保留。

按下Cmd+R來運行下你的app,你應該可以瀏覽你的網站了。第一步完成!


選擇一個網站:UIAlertController

我們要鎖定這個app這樣它就只能打開用戶指定的網站。第一步是提供一些我們選好了的網站,這樣用戶可以自己選擇想要登陸的網址,也就是說,我們需要在導航欄加上一個按鈕。

在viewDidLoad()中找個地方(通常是在super.viewDidLoad()下面)添加一下代碼:

navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Open", style: .Plain, target: self, action: "openTapped")

之前的項目已經做過同樣的事情了,除了這里我們用的是一個自定義標題而不是系統圖標。它調用了openTapped()方法,目前還不存在。所以現在我們來創建它。把這個方法放到viewDidLoad()下面:

func openTapped() {

? ? ? ?let ac = UIAlertController(title: "Open page...", message: nil, preferredStyle: .ActionSheet)

? ? ? ?ac.addAction(UIAlertAction(title: "apple.com", style: .Default, handler: openPage))

? ? ? ?ac.addAction(UIAlertAction(title: "hackingwithswift.com", style: .Default, handler: openPage))

? ? ? ?ac.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))

? ? ? ?presentViewController(ac, animated: true, completion: nil)

}

警告:如果本章一開始你沒有把目標設備設置成iPhone,上面的代碼很可能無法正常工作。我跟你說過要設置成iPhone,但很多人可能直接略過了。如果你選擇的是iPad或Universal,你需要在openTapped()里,在顯示警告控制器的前面加上ac.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem。

我們已經在項目2里用過UIAlertController類了,但這里有些許不一樣,因為三個原因:

1.我們在message中使用nil,因為這個警告不需要調用方法。

2.我們用.ActionSheet是因為我們要向用戶推送更多的信息。

3.我們添加了專用取消按鈕風格.Cancel,它調用的也是nil,就是隱藏警告控制器。

我們的網站按鈕都是指向方法openPage(),雖然它還沒寫好。跟我們之前載入網頁的方式很像,但現在你至少會看到為什么UIAlertAction的操作方法要用一個參數來告訴你哪個動作被選中了。

把下面的方法直接加到方法openTapped()后面:

func openPage(action: UIAlertAction!) {

? ? ? ? let url = NSURL(string: "https://" + action.title!)!

? ? ? ? webView.loadRequest(NSURLRequest(URL: url))

}

方法只有一個參數,即用戶選中的UIAlertAction類對象。很明顯如果Cancel被觸碰它就不會被調用,因為它的操作方法是nil,而不是openPage。

方法完成的是,用動作的屬性title(apple.com等)和前面加上的“https://”來確保傳輸安全,然后以此構建一個NSURL。然后用NSURLRequest將其打包再交給網絡視圖去載入。你需要做的就是確保UIAlertController中的網址都是正確的,其他的交給方法去做。

現在你可以測試下app了,但這里還有個小改動可以讓整個體驗水平上升:把標題放到導航條中去。現在我們被網絡視圖的導航委托,意思是任何有趣的導航發生,比如網頁載入完成時,我們都會被通知到。我們要用這個來設置導航欄標題。

一旦我們告訴Swift我們的ViewController類遵從WKNavigationDelegate協議,Xcode就會及時升級它的系統來支持所有的WKNavigationDelegate方法。之后如果你在openPage()方法下面開始輸入“web”,你就會看到一系列可以用的WKNavigationDelegate方法。

滾動可選列表直到你找到didFinishNavigation后按下回車讓Xcode為你完成方法。現在把它修改成下面的樣子:

func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {

? ? ? ? title = webView.title

}

這個方法就是為了讓視圖控制器的title屬性值變成網頁視圖的標題,也就是最近載入的網頁標題。

按下Cmd+R來運行app,你會看到網站首頁,還有在載入完成時導航欄中的標題。


監控頁面載入:UIToolbar和UIProgressView

現在是個介紹兩個新的UIView子類UIToolbar和UIProgressView的好時機。UIToolbar有一系列用戶可以觸碰的UIBarButtonItem對象。我們已經見過每個視圖控制器是怎么擁有一個rightBarButton的,UIToolbar就像一個工具欄。UIProgressView是一個彩色條狀物,可以表示任務的進展程度,所以有時會被稱為“進度條”。

我們將要使用UIToolbar的方法非常簡單:當在UINavigationController中被激活時,視圖控制器自帶一個toolbarItems數組。

這跟視圖控制器被激活時rightBarButtonItem的出現方式很像。我們只需要設置好數組,然后告訴導航控制器要顯示它的工具欄就好了。

首先要創建兩個UIBarButtonItem,其中有一個有點特別是因為它是一個可變空間。這是種特別的UIBarButtonItem類型,它表現得像個噴泉,一旦它所有的空間都被用上了它就會把其他按鈕擠到一邊。

在viewDidLoad()中的rightBarButtonItem下面我們加上以下代碼:

let spacer = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace, target: nil, action: nil)

let refresh = UIBarButtonItem(barButtonSystemItem: .Refresh, target: self, action: "refreshTapped")


toolbarItems = [spacer, refresh]

navigationController?.toolbarHidden = false

第一行是新內容,至少一部分是:我們用系統自帶類型.FlexibleSpace來創建一個代表可變空間的按鈕。它不需要目標或是動作,因為不會被按。第二行我們已經見過了,只不過現在調用的是方法refreshTapped()。

最后兩行是新的:第一行把可變空間和刷新按鈕放到一個數組中,然后把它賦值給視圖控制器的toolbarItems數組。第二行把導航控制器的toolbarHidden屬性設置成假,即工具欄和它的子項都會在當前視圖中顯示。

編譯,運行。然后你會看到刷新按鈕整齊地對齊到右側——這就是可變空間的效果,它會盡可能占據左邊開始的所有空間。但如果你點擊它app就會崩潰。

回到Xcode中,你甚至可能注意到屏幕下方的窗格中出現一個很長的崩潰記錄。你要滾動到頂部然后從那里開始閱讀,因為剩下的很多現在都沒用。你會看到下面的內容:

[project4.ViewController refreshTapped]: unrecognized selector sent to instance

"selector"是方法的另一種稱呼。好吧,不是很準確的說法——它要比方法聰明點兒,但你只需要把它當做方法就可以了。所以,錯誤信息的意思是“我試著調用了refreshTapped(),但我找不到它”——這就對了!讓我們在類的最后加上它:

func refreshTapped() {

? ? ? ?webView.reload()

}

還沒完,你可以看到:WKWebView有個方法reload(),就是用來重新載入網頁的。這很簡單,所以我們可以把它直接放到UIBarButtonItem的語句里面。所以把refresh按鈕的定義改成這樣:

let refresh = UIBarButtonItem(barButtonSystemItem: .Refresh, target: webView, action: "reload")

把webView作為目標,把“reload”作為動作意思是按鈕指向webView.reload()。很簡單!

下一步是把UIProgressView加入到工具欄中,它可以顯示完成載入還要多久。它需要兩個信息:

你不能直接把隨機的UIView子類交給UIToolbar或rightBarButtonItem屬性。相反,你得先把它們打包成UIBarButtonItem,這樣就可以了。

雖然WKWebView會用它的estimatedProgress屬性告訴我們頁面載入多少了,但WKNavigationDelegate系統不會告訴我們什么時候這個值被更改了。所以,我們要通過一個叫關鍵值觀察,即KVO的強大技術來通知我們。

首先,我們創建一個進程視圖然后把它放到導航條按鈕里面。從頂部的定義開始,就放在WKWebView屬性下面:

var progressView: UIProgressView!

現在把下面的代碼直接放到viewDidLoad()中“let spacer = ”行前面:

progressView = UIProgressView(progressViewStyle: .Default)

progressView.sizeToFit()

let progressButton = UIBarButtonItem(customView: progressView)

三行都是新的:

1. 第一行創建了一個UIProgressView的實例,給了默認樣式。還有另外一種叫.Bar的樣式,不是用填充線來表示進度的,這里用默認的最好。

2.第二行讓進程視圖根據它的內容來設置布局尺寸。

3.最后一行用customView參數來創建一個新的UIBarButtonItem,也就是我們在UIBarButtonItem里面打包UIProgressView的地方,這樣它就可以直接進入我們的工具欄了。

進程控件創建好了之后,我們可以把他放進toolbar里面的任意位置。現有的spacer會自動縮小自己的尺寸來放置進程按鈕,所以我要把toolbarItems數組調整如下:

toolbarItems = [progressButton, spacer, refresh]

即進程視圖在最前面,中間留空,最后是刷新按鈕。

現在運行的話,你會看到一條代表進程的灰線——顏色值默認為0。理想情況下我們想把它設置成跟webView的estimatedProgress值相同,也就是0~1之間的一個數值,但WKNavigationDelegate不會告訴我們什么時候它的值變化了。

Apple用來解決這個問題的辦法有很多,很強大,而且無處不在,學一次就可以次次用。它叫做KVO,可以代表你跟Swift說:“請告訴我什么時候誰改變了對象Y的X屬性。”

我們要用KVO來觀察estimatedProgress屬性,真的很簡單。首先在viewDidLoad()中,我們通過添加下面的代碼來把自己作為網絡視圖中的屬性觀察者:

webView.addObserver(self, forKeyPath: "estimatedProgress", options: .New, context: nil)

方法addObserver()有四個參數:誰在觀察,觀察什么屬性,想要什么值,還有個環境值。

forKeyPath并非forProperty是因為它并不僅僅只是輸入一個屬性名。你可以指定一個路徑:一個屬性中的屬性中的屬性……更深入的關鍵路徑還可以添加功能,比如一個數組中所有元素的平均值!

context簡單點兒:就是用于定位,也就是你指定的值的變化和變化發生的環境被一并返回到你這里。

警告:在更復雜一點的應用中,每個addObserver()都得跟一個removeObserver()配對,在觀察結束時——比如你在視圖控制器里的觀察結束時。

一旦你作為觀察者使用了KVO,你必須執行observeValueForKeyPath()方法。它會告訴你什么時候觀察值發生了變化,所以,現在讓我們加上這個方法:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer) {

? ? ?if keyPath == "estimatedProgress" {

? ? ? ? ? ? ? progressView.progress = Float(webView.estimatedProgress)

? ? ? }

}

代碼表示哪個對象被改過了,同時還有之前登記過的環境,這樣你就能知道它是不是你要的那個對象。

在這個項目中,我們關心的是參數keyPath是否被賦值給estimatedProgress——即estimatedProgress的值是否被變更過。如果是,那我們就把新的estimatedProgress值賦給進程視圖的progress屬性。

小提示:estimatedProgress是Double類型的,而UIProgressView的progress是Float。Swift不允許把Double放進Float中去,所以我們得從這個Double中創建一個新的Float。


最后的重構

我們的app有個致命的漏洞,想修復的話要么讓代碼量翻倍,要么重構。一般而言,第一個選項都是最簡單的,然而反直覺的是它也是最難的。

漏洞是介個樣子的:我們讓用戶從網站列表中選擇,但一旦他們登陸所選網站之后,他們可以跟著鏈接隨便去哪兒都行。如果能確保之后跳轉的鏈接地址都在我們的安全列表上,這不是很贊的一件事嘛?

第一種辦法——復制一份代碼——即寫兩份可登陸的網址名單:一次是在UIAlertController中,另外一次是我們檢查鏈接的時候。很簡單,但也可能成為陷阱:你有兩份網址清單,而你得讓它們同時保持更新。而且如果你在復制過了的代碼中發現了bug,你會記得要在第二份里面也去修復嘛?

第二種辦法叫重構(refactoring),這是一種非常高效的重寫代碼的辦法。雖然結果也得相同。重寫的目的是為了讓它更高效,更容易讀取,降低它的復雜性,讓它更靈活。最后一項才是我們的真正目的:我們重構代碼就是為了一個共享的允許網址數組。

在定義兩個屬性webView和progressView的后面加上:

var websites = ["apple.com", "hackingwithswift.com"]

這是個包含我們允許用戶訪問的網址的數組。

利用這個數組,我們可以簡化網絡視圖的初始網址這樣就會易于編程。在viewDidLoad()中,把網頁初始化部分改成:

let url = NSURL(string: "http://" + websites[0])!

webView.loadRequest(NSURLRequest(URL: url))

目前為止還挺簡單的。下一個改動就是讓UIAlertController為它的一系列UIAlertAction來使用網址。找到openTapped()方法,然后把下面的兩行:

ac.addAction(UIAlertAction(title: "apple.com", style: .Default, handler: openPage))

ac.addAction(UIAlertAction(title: "hackingwithswift.com", style: .Default, handler: openPage))

改成下面的循環:

for website in websites {

? ? ? ac.addAction(UIAlertAction(title: website, style: .Default, handler: openPage))

}

這會為我們數組中個每個網址都添加一個UIAlertAction對象,也不太復雜。

最后一個改動是新的,屬于WKNavigationDelegate協議。如果你找個地方輸入以web開頭的新方法,你會看到一個WKWebView相關代碼自動完成選項表。找到一個叫decidePolicyForNavigationAction的,新建一個方法。

這個委托返回允許我們決定每當什么事情發生時我們是否想讓跳轉發生。我們可以檢查跳轉在頁面的什么位置開始發生,我們可以看到它是被點擊的鏈接還是遞交的表格觸發的,或者在我們的例子中,我們可以查看是否為我們喜歡的鏈接。

現在我們執行了這一方法,它期望得到回應:是否載入頁面?這個方法被調用時,你傳入了一個decisionHandler參數。它實際包含的是一個函數,也就是說,你“調用”這個參數,實際上調用的是方法。

如果把你弄糊涂了,請讓我試著解釋下。項目2中我們聊過了閉包:一堆你可以像傳變量一樣傳入到方法中的代碼,會在稍晚一點的時間被執行。decisionHandler同樣也是閉包,只是有些不同——不是給別人一塊代碼來執行,你被要求來執行給定的代碼塊。

沒錯:你被要求用decisionHandler閉包來做點什么。聽起來就像用一種很復雜的方式來表達從方法返回一個值,但這還是低估了它的能耐!有了它你可以給用戶展示一些用戶接口“你真的想要載入這個頁面嗎?”,然后當你有了答案時調用閉包。【最后一句略別扭】

所以,我們得評估URL,看看它是否在我們的安全列表中,然后根據是/否回答來調用decisionHandler。方法的代碼如下:

func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {

? ? ? ?let url = navigationAction.request.URL

? ? ? ?if let host = url!.host {

? ? ? ? ? ? ? for website in websites ?{

? ? ? ? ? ? ? ? ? ? if host.rangeOfString(website) ?!= nil {

? ? ? ? ? ? ? ? ? ? ? ? ? ? decisionHandler(.Allow)

? ? ? ? ? ? ? ? ? ? ? ? ? ? return

? ? ? ? ? ? ? ? ? ? ?}

? ? ? ? ? ? ? ? }

? ? ? ? }

? ? ? ? decisionHandler(.Cancel)

}

讓我們詳細過一遍每一行:

1.讓常量url等于要跳轉的NSURL,這讓代碼更清楚。

2.我們用if/let句法來解包可選項url.host的值。還記得我說過NSURL可以幫你正確地分析URL嗎?這就是個好例子:這行在說,“如果這個URL有主機,把它拉出來”——“主機”表示網站域名,就像apple.com一樣。PS:我們得非常小心地解包,因為不是所有的連接都有主機。

3.我們循環了安全列表中的所有網站,把站名放在website變量中。

4.我們用rangeOfString()方法來檢查是不是每個安全地址都出現在主機名的某個位置上。

5.如果網址被發現了(即rangeOfString()返回的不是nil),那我們就調用decision handler:允許載入。

6.找到網址,調用了decisionHandler之后我們使用return語句。就是說“現在離開方法了。”

7.如果沒有主機,或者循環結束我們也沒找到要訪問的網址,我們調用decisionhandler來取消載入。

rangeOfString()方法可以有好幾個參數,只有第一個是可選參數,所以上面的用法是可以的。在一個字符串上調用它,然后把另外一個字符串作為參數,然后它就會告訴你它是在哪兒被發現的,或者它沒被發現(即nil)。

你已經在項目1中碰到過hasPrefix()了,但它在這里不合適是因為我們的安全網址名可以在URL的任意位置出現。比如,slashdot.org指向的是移動網址m.slashdot.org,而hasPrefix()就會在這個測試中失敗。

返回語句是新的,但接下來它會很常用。它直接跳出方法,不再執行任何代碼。如果你說你的代碼返回了一個值,你就會用return來做這件事。

你的項目完成了:按下Cmd+R來體驗下!


總結

又一個程序完成了,又學了很多東西。這一節你已經學到了loadView(),WKWebView,委托,類和結構體,NSURLRequest,UIToolbar,UIProgressView,KVO等,所以你應該對你的成就感到自豪!

這個項目還有很大的提升空間,你從哪里開始由你自己決定。我會建議你調查,如果初始視圖控制器改成表視圖,這樣用戶就可以從列表中自由選擇而不是只有數組中的前兩個。

一旦你完成了項目5,你可能會想要返回這里來增加從文件里載入列表網站的選項,而不是數組里的硬編碼。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容