`
啸笑天
  • 浏览: 3465703 次
  • 性别: Icon_minigender_1
  • 来自: China
社区版块
存档分类
最新评论

iOS 8 WebKit框架概览

    博客分类:
  • ios
 
阅读更多

转自:http://www.cocoachina.com/ios/20150203/11089.html   http://www.cocoachina.com/ios/20150205/11108.html

(原文: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框架。

1
import WebKit

将下面的变量加入到类中

1
var webView: WKWebView

将下面的方法加入到类中。它将会初始化webview并设置其frame为0.稍后我们会使用自动布局(auto layout)来给webview添加约束,这样这个webview就能在任何苹果设备和任何方向上正常工作了。

1
2
3
4
required init(coder aDecoder: NSCoder) {
    self.webView = WKWebView(frame: CGRectZero)
    super.init(coder: aDecoder)
}

在viewDidLoad()底部,添加如下语句。这样这个webView就被添加到主视图了。

1
view.addSubView(webView)

接下来,在viewDidLoad()方法底部添加如下约束

1
2
3
4
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()底部添加如下代码

1
2
3
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添加约束,我们将会以代码的形式调整其大小。

1
@IBoutlet weak var barView: UIView!

在viewDidLoad()的super.viewDidLoad()之后添加如下代码

1
barView.frame = CGRect(x:0, y: 0, width: view.frame.width, height: 30)

这段代码设置了barView的大小。

将下面的方法添加到类

1
2
3
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。你应会看到如下代码。

1
@IBOutlet weak var urlField: UITextField!

我们希望viewcontroller能成为UITextFieldDelegate的代理,在storyboard的Document Outline中,按下control然后将textfield拖放到viewcontroller,然后在弹出视图选择delegate。

选择textfield控件,在属性检查(右边第四栏)设置如下属性。

  1. Clear Button:Appears while editing

  2. Correction: NO

  3. Keyboard Type:URL

  4. Return Key:Go

在类声明部分添加实现UITextFieldDelegate代码。

1
class ViewController: UIViewController, UITextFieldDelegate

接下来添加如下UITextFieldDelegate代码

1
2
3
4
5
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。

1
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。你应该会得到如下代码

1
2
3
@IBOutlet weak var backButton: UIBarButtonItem!
@IBOutlet weak var forwardButton: UIBarButtonItem!
@IBOutlet weak var reloadButton: UIBarButtonItem!

然后为每个按钮依次分别创建back,forward和reload方法。将每个action的Type更改为UIBarButtonItem。你应该会得到如下代码。

1
2
3
4
5
6
7
8
@IBAction func back(sender: UIBarButtonItem) {
}
 
@IBAction func forward(sender: UIBarButtonItem) {
}
 
@IBAction func reload(sender: UIBarButtonItem) {
}

在viewDidLoad()的底部添加如下代码。我们不希望后退和前进按钮在App被启动时就可点击。

1
2
backButton.enabled = false
forwardButton.enabled = false

在viewDidLoad()的约束条件添加代码之后,创建并载入一个任务代码之前,添加如下代码。这句代码使得这个类成为了loading属性的监听者。

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

添加下面的方法到类。这个方法将会在可监听的属性变化时执行。后退和前进按钮将根据当前webview的状态来决定是否可被点击。

1
2
3
4
5
6
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()方法。

1
2
3
4
5
6
7
8
9
10
11
12
@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,我们需要写代码来获取这一错误并提示用户。

首先修改类声明,如下所示:

1
class ViewController: UIViewController, UITextFieldDelegate, WKNavigationDelegate

WKWebView有一个属性navigationDelegate,接受一个实现WKNavigationDelegate协议的对象。这个协议提供了多种方法来处理导航事件,包括载入错误。

在init()底部添加如下代码。通过下面的代码,我们将这个类设置成了webview的navigation代理。

1
self.webView.navigationDelegate = self

接下来添加如下方法。这个代理方法将会在有错误发生时被调用。

1
2
3
4
5
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。你应该会得到下面的代码。

1
@IBOutlet weak var progressView: UIProgressView!

在ViewController中,替换西面的代码

1
view.addSubview(webView)

1
view.insertSubview(webView, belowSubview: progressView)

在viewDidLoad()中创建和载入一个url任务之前添加下面的代码,调用webview的addObserver方法。

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

在observeValueForKeyPath()方法中的其他if语句之后添加如下代码。

1
2
3
4
if (keyPath == "estimatedProgress") {
    progressView.hidden = webView.estimatedProgress == 1
    progressView.setProgress(Float(webView.estimatedProgress), animated: true)
}

这段代码将会更新progressview的进度,如果加载完毕会隐藏progressview。

在类中添加如下代码。这是一个WKNavigationDelegate的代理方法,将会在页面载入完毕后执行。当一个任务完成后,我们使用它来重置progress view的进度。

1
2
3
func webView(webView: WKWebView!, didFinishNavigation navigation: WKNavigation!) {
    progressView.setProgress(0.0, animated: false)
}

运行程序,当网页加载时,你将会看到一个蓝色的进度条。

总结

我们已经了解了WebKit的基础部分。我们看到如何添加一些和Safari相似的功能,如载入url,浏览历史,检测错误,展示进度。在这个教程的第二部分,我们会深入了解如何将一个JavaScript注入网页,来构建一个更强大的程序。

你可以通过这个链接来下载这个教程的实例代码。

 

 

 

 

(原文: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中加入如下方法。

1
2
3
4
5
6
7
8
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()方法下面加入如下代码:

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

然后在observeValueForKeyPath(_:, ofObject:)方法其他if语句下方加入如下代码。

1
2
3
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规则,如下所示。

1
2
div#sidebar {
}

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

1
display:none;

在你添加完上述代码后,侧边栏应该会从页面消失。

现在删除这条布局规则以显示侧边栏。现在我们要使用JavaScript往DOM中添加代码。在html页面之下,是运行JavaScript的命令行。将如下代码粘贴到命令行。

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

上述代码创建了一个元素并赋值给了一个变量。接下来我们如下代码,他将会给这个元素添加css规则。我也把底部书籍栏目也添加了进去。

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

最后,使用下面的代码给DOM添加样式标签。这段代码会马上执行,侧边栏和底部的书籍栏目会消失。

1
document.documentElement.appendChild(styleTag);

上面的几个过程是隐藏页面元素的必须过程。

回到Xcode,创建一个新文件File->New->File->iOS->Other->Empty并命名为hideSection.js。添加如下代码。

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

在ViewController中,替换init()中方法为如下:

1
2
3
4
5
6
7
8
9
10
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的数据将会被打印到命令行。

1
2
3
4
5
6
7
8
9
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。粘贴如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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。你应该会看到如下代码。

1
@IBOutlet weak var recentPostsButton: UIBarButtonItem!

在viewDidLoad()方法底部,添加如下代码。我们希望这个按钮一直不可点,直到posts数组有了数据。

1
recentPostsButton.enabled = false

在ViewController,import语句下面添加如下代码。

1
let MessageHandler = "didGetPosts"

在类文件中添加如下属性。

1
var postsWebView: WKWebView?

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

1
2
3
4
5
6
7
8
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协议。

1
class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler

我们建立一个模型(model)文件来保存文章数据。创建一个文件File->New->File->iOS->Source->Cocoa Touch Class。命名为Post并作为NSObject的子类。在类中粘贴如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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类中添加如下变量。

1
var posts: [Post] = []

添加WKScripMessageHandler协议必须遵守的方法。

1
2
3
4
5
6
7
8
9
10
11
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添加如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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。

1
2
3
4
5
6
7
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语句下面。

1
let PostSelected = "postSelected"

当点击一个cell的时候,我们将发送一个通知。上面的常量就是这个通知的名字。

在PostsTableViewController中添加如下方法。

1
2
3
4
5
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()方法底部添加如下代码。

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

上述代码将这个ViewController设置为了cell点击发送的通知的观察者(observer)。

在ViewController中添加如下方法。

1
2
3
4
5
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。按照下面的代码编辑这个方法。

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

现在你应该有一个取消按钮了,它可以用来隐藏这个Table view。

image05

结论

新的WebKit框架使得开发者能够让App和网页内容之间实现无缝交互。我们学习了如何自定义网页样式。从网页中提取数据,并在App中使用这些数据。

如果你的App只是一个网页版App的容器,使用WebKit框架吧!它将带来如原生App般的性能和操作体验。WebKit框架将会为这些体验不好的App力挽狂澜。

如果你想了解更多关于此框架的内容,这个WWDC视频将是个非常好的开始。

你可以在这里下载完整项目。

分享到:
评论
5 楼 啸笑天 2016-07-08  
http://www.cocoachina.com/bbs/read.php?tid-1677270-fpage-3.html   wkwebview 获取 JSContext问题 已解决  
4 楼 啸笑天 2016-07-08  
http://liuyanwei.jumppo.com/2015/10/17/ios-webView.html   UIWebView和WKWebView的使用及js交互
3 楼 啸笑天 2016-07-08  
iOS 8 引入WKWebView, WKWebView 不支持JavaScriptCore的方式但提供message handler的方式为JavaScript 与Objective-C 通信.
在Objective-C 中使用WKWebView的以下方法调用JavaScript:
- (void)evaluateJavaScript:(NSString *)javaScriptString
         completionHandler:(void (^)(id, NSError *))completionHandler
如果JavaScript 代码出错, 可以在completionHandler 进行处理.
在Objective-C 中注册 message handler:
// WKScriptMessageHandler protocol?

- (void)userContentController:(WKUserContentController *)userContentController
    didReceiveScriptMessage:(WKScriptMessage *)message
{
    NSLog(@"Message: %@", message.body);
}

[userContentController addScriptMessageHandler:handler name:@"myName"];
在JavaScript 将信息发给Objective-C:
// window.webkit.messageHandlers.<name>.postMessage();?

function postMyMessage()? {?
    var message = { 'message' : 'Hello, World!', 'numbers' : [ 1, 2, 3 ] };?
    window.webkit.messageHandlers.myName.postMessage(message);?
}
2 楼 啸笑天 2016-01-26  
http://blog.csdn.net/woaifen3344/article/details/49452227  WKWebView新特性及JS交互
1 楼 啸笑天 2016-01-18  
苹果文档:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/WebKit/ObjC_classic/index.html

相关推荐

    WebKit和Chromium源码及原理剖析.pdf

    #### 第8篇 网页在Safari快速滚动和回弹的原理:-webkit-overflow-scrolling : touch;的实现 - **-webkit-overflow-scrolling属性解释**: - `-webkit-overflow-scrolling: touch;`是一个CSS属性,用于控制元素...

    WebKit Objective-C Programming Guide

    ### WebKit Objective-C编程指南概览 #### 一、引言与WebKit介绍 **WebKit**是一种开源的浏览器引擎,由苹果公司开发并维护,用于在iOS、macOS和其他平台上渲染网页内容。它不仅支撑了Safari浏览器的核心功能,还...

    移动终端操作系统架构概览

    ### 移动终端操作系统架构概览 #### 一、引言 随着移动互联网技术的快速发展,移动终端操作系统作为连接用户与应用的重要桥梁,在日常生活和工作中扮演着越来越重要的角色。本篇将详细介绍移动终端操作系统的架构...

    java引用传递笔试题abc-javaScript_call_native:一个javaScript与Native原生App之间互调的demo

    是一个苹果在iOS7引入的框架,该框架让 Objective-C 和 JavaScript 代码直接的交互变得更加的简单方便。 JavaScriptCore 是苹果Safari浏览器JavaScript引擎,或许你听过Google的V8引擎,在WWDC上苹果演示了最新的...

    移动终端操作系统架构概览解剖.docx

    ### 移动终端操作系统架构概览 #### 一、引言 随着移动互联网技术的快速发展,移动终端操作系统作为连接用户与互联网服务的关键桥梁,其重要性日益凸显。本文旨在通过对移动终端操作系统架构进行深入解析,帮助读者...

    CFNetwork 编程指南.pdf

    - **软件层次结构**:CFNetwork位于底层,之上是更高层次的框架,如Cocoa和WebKit等。 #### 7. CFNetwork的使用场景 - **优势对比**:相较于传统的BSD sockets,CFNetwork具有更高的集成度,能够更好地与基于“运行...

    jQuery_Mobile中文手册

    - **WebKit和HTML5**:WebKit是一种浏览器引擎,被广泛应用于iOS中的Mobile Safari浏览器以及Android系统中。它支持HTML5的标准,这使得使用jQuery Mobile时能够充分利用HTML5的新特性和性能优势。 #### 七、移动...

    jQuery_Mobile中文手册.pdf

    ### jQuery Mobile 中文手册知识点概览 #### 一、概述 **jQuery Mobile** 是一个用于构建适应各种智能手机和平板电脑的高性能移动Web应用程序的开源框架。它基于jQuery库,为开发者提供了一种简单的方式来创建优雅...

    jQuery+Mobile中文手册

    1. **统一的UI框架**:jQuery Mobile支持多种移动平台,如iOS、Android、BlackBerry OS 6.0、WebOS、Firefox OS和Opera Mobile,简化了跨平台开发工作。 2. **兼容性广泛**:不仅支持当前主流移动平台,还计划拓展...

    jQuery Mobile中文手册

    ### jQuery Mobile 中文手册知识点概览 #### 一、概述 **jQuery Mobile**是一款非常流行的移动Web应用开发框架,它允许开发者使用HTML5、CSS3和JavaScript为多种移动设备创建一致且美观的应用程序界面。本手册由...

    Android应用开发

    此外,还包括了媒体库、SQLite数据库、Webkit引擎等。 3. **应用框架层**:这一层是开发者创建应用时所使用的API集合。它提供了各种服务和数据库访问工具,比如位置服务、视图系统、资源管理器、通知管理器等,使得...

    Browser:简单的Qt Web浏览器

    Qt支持多种操作系统,包括Windows、Linux、macOS、Android和iOS。Qt的事件驱动模型和组件化设计使得开发者能够快速构建功能丰富的应用。 **QtWebKit** QtWebKit是Qt库中的一个模块,它基于WebKit,用于解析和显示...

    Android基础

    #### 一、Android与3G技术概览 - **3G技术定义**:3G,即第三代移动通信技术,是相对于第一代模拟制式手机(1G)和第二代数字手机(2G)而言的最新一代手机通信技术标准。3G技术不仅能够提供传统的语音通话服务,还...

    html5的学习资料

    - **跨平台**:一个应用可以同时运行在多个平台上,如 Android、iOS 等,减少了开发成本。 - **低成本**:由于使用了通用的 Web 技术,开发者不需要掌握多种编程语言,降低了学习曲线和开发成本。 - **快速响应市场...

Global site tag (gtag.js) - Google Analytics