你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> iOS 8 WebKit框架概覽(下)

iOS 8 WebKit框架概覽(下)

編輯:IOS開發基礎

(原文:A Look at the WebKit Framework in iOS 8 – Part 2 作者:Joyce Echessa 譯者:ibenjamin )

在第一部分 (中譯版)中,我們了解了WebKit框架的基礎部分。在本篇文章中,我們會深入了解WebKit框架並學習如何在原生App中定制網頁。我們也會學習如何從網頁中獲得數據,並在App中使用數據。

接下來我們將建立一個專門浏覽appcoda.com的App。首先,請下載初始項目。初始項目就是一個名為Coda的簡單浏覽器,跟我們在第一個部分編寫的App差不多。唯一的區別就是沒有textfield控件給用戶輸入url,而且我也將前進、後退和刷新按鈕更換成了圖片。

webkit-javascript

處理外部鏈接

如果你運行這個App並點擊了一個外部鏈接,webview會加載這個鏈接。但是這個App使用來浏覽Appcoda的,所以我們需要防止加載外部鏈接。如果用戶點擊了外部鏈接,這個鏈接的內容就會在Safari中打開。

我們需要的是定制網頁加載的方式。達到這個目標,我們需要干涉加載網頁的正常過程。在完成這個目標之前,讓我們先來了解一下網頁加載的過程。

網頁加載由一個動作(Action)觸發。這可能是任何導致網頁加載的動作,比如:觸碰一個鏈接、點擊後退、前進和刷新按鈕,JavaScript 設置了window.location屬性,子窗口的加載或者對WKWebView的loadRequest()方法的調用。然後一個請求被發送到了服務 器,我們會得到一個響應(可能是有意義的也可能是錯誤狀態碼,比如:404)。最後服務器會發送更多地數據,並結束加載過程。

webkit demo project

WebKit允許你的App在動作(Action)和響應(Response)階段之間注入代碼,並決定是否繼續加載,取消或是做你想做的事情。

webkit demo project

在ViewController中加入如下方法。

func webView(webView: WKWebView!, decidePolicyForNavigationAction navigationAction: WKNavigationAction!, decisionHandler: ((WKNavigationActionPolicy) -> Void)!) {
    if (navigationAction.navigationType == WKNavigationType.LinkActivated && !navigationAction.request.URL.host!.lowercaseString.hasPrefix("www.appcoda.com")) {
        UIApplication.sharedApplication().openURL(navigationAction.request.URL)
        decisionHandler(WKNavigationActionPolicy.Cancel)
    } else {
        decisionHandler(WKNavigationActionPolicy.Allow)
    }
}

上述是一個WKNavigationDelegate代理方法,在網頁加載時會被多次調用。其中一個參數WKNavigationAction對象 包含了幫助你決定是否讓一個網頁被加載的信息。在上面的代碼中,我們使用其中兩個屬性,navigationType和request。我們只想中斷被用戶點擊的外部鏈接的加載過程,所以我們檢查了navigationType。然後我們檢查了request的url來確認它是否是一個外部鏈接。如果兩個條件都滿足,這個url就會在用戶的浏覽器中打開(通常都是Safari)並且WKNavigationActionPolicy.Cancel終止了 App加載網頁的過程。否則這個網頁就會被加載並顯示。

運行這個程序,點擊任何外部鏈接,這個鏈接都會在Safari中被加載。

設置網頁標題

如果網頁能有標題來提示用戶在哪裡的話,這將會非常有用。在前面的文章中,我們學習了一些WKWebView的KVO屬性比如loading和estimatedProgress。title也是一個KVO屬性,我們將用它來獲得當前網頁的標題。

在viewDidLoad()其他addObserver()方法下面加入如下代碼:

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

然後在observeValueForKeyPath(_:, ofObject:)方法其他if語句下方加入如下代碼。

if (keyPath == "title") {
    title = webView.title
}

運行程序,隨便逛逛,你將發現navigationbar的title會被正確地更新。

image03

修改網頁內容

現在這個Coda App是一個Appcoda的專用浏覽器,但是我們還可以做幾樣事情來提升一下用戶體驗。

因為設備的特性,移動App以簡明的方式展示數據和信息。用戶希望能看到他們想看的東西,而且不用做大量的滑動來獲得信息。

目前為止,這個App展示了Appcoda網頁的所有內容。我們想忽略某些和網頁內容相關程度不大的東西。我們將會移除側邊欄和底部展示《Appcoda Swift book》的欄目。

為了達到這個目標,我們使用JavaScript向網頁注入CSS規則以隱藏這些欄目。首先,我們需要檢查網頁然後決定規則。

為了檢查網頁,我們使用大多數浏覽器都支持的開發者工具。你也可以自己以插件(plugins)或者add-ons的形式安裝到你的浏覽器,比如火狐的Firebug。我將使用Chrome的開發者工具,但你可以使用任何你喜歡的。其過程大致一樣。

打開Chrome開發者工具,View->Developer->Developer Tools。

這將在浏覽器底部打開一個開發者窗口。開發者窗口將和上半部分左邊的網頁源碼和郵編的CSS樣式查看分離開來。在底部,是JavaScript命令行,這裡你可以輸入你的代碼,它將會在網頁執行。

我們需要檢查id屬性然後標記處我們想要隱藏的欄目。

側邊欄會在所有的網頁中顯示,而底部的書籍欄目只會在文章頁面顯示。點擊任意一篇文章,打開開發者工具。首先,右擊側邊欄並選擇檢查元素。在開發者 窗口中,會高亮顯示對應的元素代碼。如果你將你的鼠標移動到對應的代碼,網頁部分相對應的區域也會高亮顯示。我們希望得到包含了整個側邊欄的根元素 id(或者class)。

根據你選擇審查元素時所處的位置,向上折疊標簽直到只有側邊欄在頁面中被高亮顯示。上一個被折疊的標簽就是我們要的根元素。在這裡,他是一個div標簽,id為’sidebar‘。

在將代碼寫入你的App之前,你最好在浏覽器中測試一下它。因為如果發生了什麼錯誤的話,在App中調試會非常困難。我們首先在浏覽器中測試CSS和JavaScript。

點擊我們在上面找到的div標簽。在窗口的右邊你將會看見它的CSS布局。點擊+按鈕添加一條CSS規則,如下所示。

div#sidebar {
}

在上面的代碼中添加如下代碼:

display:none;

在你添加完上述代碼後,側邊欄應該會從頁面消失。

現在刪除這條布局規則以顯示側邊欄。現在我們要使用JavaScript往DOM中添加代碼。在html頁面之下,是運行JavaScript的命令行。將如下代碼粘貼到命令行。

var styleTag = document.createElement("style");

上述代碼創建了一個元素並賦值給了一個變量。接下來我們如下代碼,他將會給這個元素添加css規則。我也把底部書籍欄目也添加了進去。

styleTag.textContent = 'div#sidebar, .after-post.widget-area {display:none;}';

最後,使用下面的代碼給DOM添加樣式標簽。這段代碼會馬上執行,側邊欄和底部的書籍欄目會消失。

document.documentElement.appendChild(styleTag);

上面的幾個過程是隱藏頁面元素的必須過程。

回到Xcode,創建一個新文件File->New->File->iOS->Other->Empty並命名為hideSection.js。添加如下代碼。

var styleTag = document.createElement("style");
styleTag.textContent = 'div#sidebar, .after-post.widget-area {display:none;}';
document.documentElement.appendChild(styleTag);

在ViewController中,替換init()中方法為如下:

required init(coder aDecoder: NSCoder) {
    let config = WKWebViewConfiguration()
    let scriptURL = NSBundle.mainBundle().pathForResource("hideSections", ofType: "js")
    let scriptContent = String(contentsOfFile:scriptURL!, encoding:NSUTF8StringEncoding, error: nil)
    let script = WKUserScript(source: scriptContent!, injectionTime: .AtDocumentStart, forMainFrameOnly: true)
    config.userContentController.addUserScript(script)
    self.webView = WKWebView(frame: CGRectZero, configuration: config)
    super.init(coder: aDecoder)
    self.webView.navigationDelegate = self
}

上述代碼創建了一個WKWebViewConfiguration對象,它擁有一些屬性來作為原生代碼和網頁之間溝通的橋梁。JavaScript 代碼被一個WKUserScript對象加載和包裝。然後這個腳本被賦值給WKWebViewConfiguration對象的 userContentController屬性,接著webView使用這個配置來初始化。

當創建WKUserScript對象時,我們決定這個腳本什麼時候應該被注入,和被作用於整個頁面或者某個特定的frame。

運行程序,你不在會看到側邊欄(在iPhone中,它將會在頁面底部以下的區域顯示)和底部書籍欄目了。

提取網頁數據

Appcoda的主頁顯示最近的10篇文章。當我們在設備上浏覽主頁時,你必須滑動許多次以看到底部的內容。我們希望有一個更簡單地方式來獲取最近的文章。我們將創建一個tableview來保存最近的文章。

我們通過提取網頁數據來創建這個tableview。這裡我不再會注入html了。我將會給出一段我所使用的用來獲得文章的JavaScript代碼,並解釋它如何工作。

如果你在主頁運行如下JavaScript代碼,一列包含這些文章的標題和url的數據將會被打印到命令行。

var postsWrapper = document.querySelector('#content')
var posts = postsWrapper.querySelectorAll('.post.type-post.status-publish')

for (var i = 0; i < posts.length; i++) {
    var post = posts[i];
    var postTitle = post.querySelector('h2.entry-title a').textContent;
    var postURL = post.querySelector('h2.entry-title a').getAttribute('href');
    console.log("Title: ", postTitle, " URL: ", postURL);
}

如果你觀察網頁文章部分的html結構,你回發現類似下面的東西。

360桌面截圖20150204172214.jpg

在上面的JavaScript代碼中,我們通過‘content’id獲得元素。這個是一個div元素,文章列表的中間父元素。我們將會獲得這個 div下的所有子元素,然後賦值給posts變量。它將會持有一個class為post的div數組。我們遍歷這個數組,獲得每個h2標簽中得得文本。我 們也通過另外一個鏈接標記的href屬性來獲得每個文章的URL。然後我們打印這些內容。

打開Xcode,創建一個新文件File->New->File->iOS->Other->Empth。命名為getPost.js。粘貼如下代碼。

var postsWrapper = document.querySelector('#content')
var posts = postsWrapper.querySelectorAll('.post.type-post.status-publish')

function parsePosts() {
    pos = []
   
    for (var i = 0; i < posts.length; i++) {
        var post = posts[i];
        var postTitle = post.querySelector('h2.entry-title a').textContent;
        var postURL = post.querySelector('h2.entry-title a').getAttribute('href');
        pos.push({'postTitle' : postTitle, 'postURL' : postURL});
    }
   
    return pos
}

var postsList = parsePosts();
webkit.messageHandlers.didGetPosts.postMessage(postsList);

上面的代碼獲得了所有文章的標題和url並把他們保存到了一個數組。最後一行代碼是的JavaScript和原生代碼之間能夠交流。 webkit.messageHandlers是一個全局對象,用來幫助觸發原生代碼回調。didGetPosts代表了和一個原生代碼方法一樣名字的消 息。postMessage向回調中傳遞了postsList數組。

在故事板中,拖放一個導航欄按鈕到導航欄的左邊。並改變它的名稱為‘Recent’。然後創建一個它的outlet並命名為recentPostsButton。你應該會看到如下代碼。

@IBOutlet weak var recentPostsButton: UIBarButtonItem!

在viewDidLoad()方法底部,添加如下代碼。我們希望這個按鈕一直不可點,直到posts數組有了數據。

recentPostsButton.enabled = false

在ViewController,import語句下面添加如下代碼。

let MessageHandler = "didGetPosts"

在類文件中添加如下屬性。

var postsWebView: WKWebView?

在viewDidLoad()底部添加如下代碼。

let config = WKWebViewConfiguration()
let scriptURL = NSBundle.mainBundle().pathForResource("getPosts", ofType: "js")
let scriptContent = String(contentsOfFile:scriptURL!, encoding:NSUTF8StringEncoding, error: nil)
let script = WKUserScript(source: scriptContent!, injectionTime: .AtDocumentEnd, forMainFrameOnly: true)
config.userContentController.addUserScript(script)
config.userContentController.addScriptMessageHandler(self, name: MessageHandler)
postsWebView = WKWebView(frame: CGRectZero, configuration: config)
postsWebView!.loadRequest(NSURLRequest(URL:NSURL(string:"http://www.appcoda.com")!))

這裡我們像之前一樣導入一個JavaScript文件,我們只希望DOM被構建好時及.AtDocumentEnd時被注入一次。我們也將MessageHandler加入了WKWebViewConfiguration作為WKWebView初始化的配置。

更新類聲明,遵循WKScriptMessageHandler協議。

class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler

我們建立一個模型(model)文件來保存文章數據。創建一個文件File->New->File->iOS->Source->Cocoa Touch Class。命名為Post並作為NSObject的子類。在類中粘貼如下代碼。

import UIKit

class Post: NSObject {
   
    var postTitle: String = ""
    var postURL: String = ""
   
    init(dictionary: Dictionary) {
        self.postTitle = dictionary["postTitle"]!
        self.postURL = dictionary["postURL"]!
        super.init()
    }
   
}

在ViewController類中添加如下變量。

var posts: [Post] = []

添加WKScripMessageHandler協議必須遵守的方法。

func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
    if (message.name == MessageHandler) {
        if let postsList = message.body as? [Dictionary] {
            for ps in postsList {
                let post = Post(dictionary: ps)
                posts.append(post)
            }
            recentPostsButton.enabled = true
        }
    }
}

上面的代碼首先將檢查接收到得消息是否是我們想要的,如果是,就會將消息中的數據提取成一個字典數組,然後使用其中的字典創建Post對象,並將這些Post對象依次添加到posts數組中,最後recentPostsButton就可被點擊了。

打開故事版,在畫板中添加一個Table View Controller。選擇它,使用Editor->Embed In->Navigation Controller將它嵌入一個navigation controller。

按下Control,點擊View Controller中得Recent按鈕,拖到這個新的navigation controller中,選擇popover presentation from the popup。選擇這個被新創建了segue,設置它的Identifier為‘recentPosts’。

創建一個新文件File->New->File->iOS->Source->Cocoa Touch class。命名為PostsTableViewController並選擇為UITableViewController的子類。

在故事板中,選擇創建的Table View Controller,選擇 Identity Inspector,設置class為PostsTableViewController。選擇table view的prototype cell,在Attributes Inspector中設置Identifier為postCell。

向PostTableViewController添加如下代碼。

import UIKit

class PostsTableViewController: UITableViewController {

    var posts: [Post] = []
   
    override init(style: UITableViewStyle) {
        super.init(style: style)
    }
   
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
   
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "Recent Articles"
        tableView.reloadData()
    }
   
    override func numberOfSectionsInTableView(tableView:
        UITableView?) -> Int {
        return 1
    }
   
    override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
        return posts.count
    }
   
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("postCell", forIndexPath: indexPath) as UITableViewCell
        let post = posts[indexPath.row]
        cell.textLabel?.text = post.postTitle
        return cell
    }
}

這裡我們實現了tableview的數據源,他將會顯示文章的標題。

添加下面代碼到ViewController。

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
    if (segue.identifier == "recentPosts") {
        let navigationController = segue.destinationViewController as UINavigationController
        let postsViewController = navigationController.topViewController as PostsTableViewController
        postsViewController.posts = posts
    }
}

當點擊Recent按鈕時,此方法會被調用。在顯示tableview View controller之前,它將posts數組傳遞給了tableview view controller。

運行程序。點擊Recent按鈕,你會看到一個充滿了文章列表的tableview。在iPhone上,它已滿屏顯示,在iPad在一個popover中顯示。

image04

當你點擊一個cell的時候,沒有任何事情發生。我們希望被點擊的文章被加載到web view上面。

在ViewController中,添加如下代碼到import語句下面。

let PostSelected = "postSelected"

當點擊一個cell的時候,我們將發送一個通知。上面的常量就是這個通知的名字。

在PostsTableViewController中添加如下方法。

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let post = posts[indexPath.row]
    NSNotificationCenter.defaultCenter().postNotificationName(PostSelected, object: post)
    dismissViewControllerAnimated(true, completion: nil)
}

在上述方法中,每當一個cell被點擊的時候會發送一個通知並隱藏(dismiss)這個tableview controller。

在ViewController類中,viewDidLoad()方法底部添加如下代碼。

NSNotificationCenter.defaultCenter().addObserver(self, selector: "postSelected:", name: PostSelected, object: nil)

上述代碼將這個ViewController設置為了cell點擊發送的通知的觀察者(observer)。

在ViewController中添加如下方法。

func postSelected(notification:NSNotification) {
    webView.loadRequest(NSURLRequest())
    let post = notification.object as Post
    webView.loadRequest(NSURLRequest(URL:NSURL(string:post.postURL)!))
}

通過上面的方法我們得到了通知中附加的post,然後加載了post中的url。

運行程序,你應該可以在tableview中的任意文章之間切換了。

到目前為止,當我們點擊Recent按鈕時,我們無法隱藏(dismissing)tableview,除非我們選擇並點擊一篇文章。我們需要添加一個取消按鈕。

在故事版中,在Table view controller的導航欄(navigationbar)的右邊添加一個按鈕。設置它的Identifier為Cancel。

打開Assistan Editor,按下Control點擊Cancel按鈕拖動到PostTableViewController類中創建一個方法。命名為cancel,確保其參數類型為UIBarButtonItem。按照下面的代碼編輯這個方法。

@IBAction func cancel(sender: UIBarButtonItem) {
    dismissViewControllerAnimated(true, completion: nil)
}

現在你應該有一個取消按鈕了,它可以用來隱藏這個Table view。

image05

結論

新的WebKit框架使得開發者能夠讓App和網頁內容之間實現無縫交互。我們學習了如何自定義網頁樣式。從網頁中提取數據,並在App中使用這些數據。

如果你的App只是一個網頁版App的容器,使用WebKit框架吧!它將帶來如原生App般的性能和操作體驗。WebKit框架將會為這些體驗不好的App力挽狂瀾。

如果你想了解更多關於此框架的內容,這個WWDC視頻將是個非常好的開始。

你可以在這裡下載完整項目。

(本文為CocoaChina組織翻譯,本譯文權利歸譯者所有,未經允許禁止轉載。)

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved