WKWebView
iOS 与 web 之间的关系非常复杂,这种复杂关系甚至可以追溯到几十年前系统建立初期。
其实现在很难说清第一代 iPhone 横空出世是一件多么困难的事情。我们现今司空见惯的触摸屏在当时只是诸多方案中的一种。最早期的产品原型是物理键盘、触摸屏、触控笔的结合,屏幕尺寸才是 5” x 7”。甚至当时 iPod 的轮子都是一个严肃的备选方案。
但最最重要的决定或许都是由软件而非硬件决定的。
iPhone 应该如何运行软件呢?像 OS X 上的应用程序,或者 web 页面,以及 Safari 都应该如何运行起来呢?仿照 OS X 去构建 iPhone OS 的方法已经广泛地被大家熟知了,这种方法到今天也留下了不少争议。
我们来回忆一下 Steve Jobs 2007 年 WWDC keynote 上这句臭名昭著的话:
iPhone 包含了整个 Safari 引擎。因此,你可以创作应用 Ajax 技术的 Web 2.0 应用,表现上和使用上都和 iPhone 原生应用一模一样。这些应用可以和 iPhone 的各种服务完美地集成到一起:这些应用可以有打电话的功能,可以发邮件,可以在 Google Maps 上寻找地标。
Web 一直是 iOS 系统上的二级公民_(讽刺的是,其实现今移动网页响应式设计的出现大多是 iPhone 推动的)_。UIWeb
笨重难用,还有内存泄漏,和 Nirtro JavaScript 引擎谈笑风生的 Safari 不知道要比它高到哪里去了。
然而,这所有的一切都会因为 WKWeb
和 Web
框架其他部分的出现而发生改变。
WKWeb
是现代 WebKit API 在 iOS 8 和 OS X Yosemite 应用中的核心部分。它代替了 UIKit 中的 UIWeb
和 AppKit 中的 Web
,提供了统一的跨双平台 API。
自诩拥有 60fps 滚动刷新率、内置手势、高效的 app 和 web 信息交换通道、和 Safari 相同的 JavaScript 引擎,WKWeb
毫无疑问地成为了 WWDC 2014 上的最亮点。
UIWeb
& UIWeb
这个两个东西是如何在 WKWebKit 中被重构成 14 个类 3 个协议的呢。虽然这次的变化确实带来了不少的新功能,但请一定不要因此感到恐慌!
WKWebKit Framework
Classes
WKBack
: 之前访问过的 web 页面的列表,可以通过后退和前进动作来访问到。Forward List
WKBack
: webview 中后退列表里的某一个网页。Forward List Item WKFrame
: 包含一个网页的布局信息。Info WKNavigation
: 包含一个网页的加载进度信息。
WKNavigation
: 包含可能让网页导航变化的信息,用于判断是否做出导航变化。Action WKNavigation
: 包含可能让网页导航变化的返回内容信息,用于判断是否做出导航变化。Response WKPreferences
: 概括一个 webview 的偏好设置。WKProcess
: 表示一个 web 内容加载池。Pool WKUser
: 提供使用 JavaScript post 信息和注射 script 的方法。Content Controller
WKScript
: 包含网页发出的信息。Message WKUser
: 表示可以被网页接受的用户脚本。Script
WKWeb
: 初始化 webview 的设置。View Configuration WKWindow
: 指定加载新网页时的窗口属性。Features
Protocols
WKNavigation
: 提供了追踪主窗口网页加载过程和判断主窗口和子窗口是否进行页面加载新页面的相关方法。Delegate WKScript
: 提供从网页中收消息的回调方法。Message Handler WKUIDelegate
: 提供用原生控件显示网页的方法回调。
UIWeb View
和 WKWeb View
的 API 区别
WKWeb
继承了 UIWeb
大部分的接口,这让 app 来继承 WKWebKit 也简单了许多(同时随着更新 iOS 8 的越来越多这也成为了某种必需)。
有兴趣的同学可以看一下这两个类的 API 区别:
UIWebView | WKWebView |
---|---|
var scroll |
var scroll |
var configuration: WKWeb |
|
var delegate: UIWeb |
var UIDelegate: WKUIDelegate! |
var navigation |
|
var back |
页面加载
UIWebView | WKWebView |
---|---|
func load |
func load |
func load |
func load |
func load |
|
var estimated |
|
var has |
|
func reload() |
func reload() -> WKNavigation! |
func reload |
|
func stop |
func stop |
var request: NSURLRequest! { get } |
|
var URL: NSURL! { get } |
|
var title: String! { get } |
访问历史
UIWebView | WKWebView |
---|---|
func go |
|
func go |
func go |
func go |
func go |
var can |
var can |
var can |
var can |
var loading: Bool { get } |
var loading: Bool { get } |
调用 Javascript
UIWebView | WKWebView |
---|---|
func string |
|
func evaluate |
原生混用
UIWebView | WKWebView |
---|---|
var keyboard |
|
var scales |
|
var allows |
页码
WKWeb
目前缺少关于页码相关的 API。
var pagination
Mode: UIWeb Pagination Mode var pagination
Breaking Mode: UIWeb Pagination Breaking Mode var page
Length: CGFloat var gap
Between Pages: CGFloat var page
Count: Int { get }
WKWeb View Configuration
重构分离开的 下面这些 UIWeb
的属性被重构进了在初始化 WKWeb
传入的设置对象:
var allows
Inline Media Playback: Bool var media
Playback Requires User Action: Bool var media
Playback Allows Air Play: Bool var suppresses
Incremental Rendering: Bool
JavaScript ↔︎ Swift 对话机制
相对于 UIWeb
最大的提升就是数据在可以 app 和 web 内容之间传递。
使用用户脚本来注入 JavaScript
WKUser
允许在正文加载之前或之后注入到页面中。这个强大的功能允许在页面中以安全且唯一的方式操作网页内容。
一个简单的例子如下,用户改变背景的用户脚本被插入到网页中:
let source = "document.body.style.background = \"#777\";"
let user Script = WKUser Script(source: source, injection Time: .At Document End, for Main Frame Only: true)
let user Content Controller = WKUser Content Controller()
user Content Controller.add User Script(user Script)
let configuration = WKWeb View Configuration()
configuration.user Content Controller = user Content Controller
self.web View = WKWeb View(frame: self.view.bounds, configuration: configuration)
WKUser
对象可以以 JavaScript 源码形式初始化,初始化时还可以传入是在加载之前还是结束时注入,以及脚本影响的是这个布局还是仅主要布局。于是用户脚本被加入到 WKUser
中,并且以 WKWeb
属性传入到 WKWeb
的初始化过程中。
这个样例可以简单扩展为更为高级的页面修改方法,例如去除广告、隐藏评论等,更复杂的样例见此:让所有出现的”the cloud”变为”my butt”。
马杀鸡 魂斗罗(Message Handlers 抱歉我憋了一天,真的不会翻译)
web 和 app 通讯机制也通过 message handler 有很大提升。
就想在Safari 审查元素功能中的 console.log
能在调试终端打印信息一样,网页中的信息也可以通过调用这个函数被传到 app 里:
window.webkit.message Handlers.{NAME}.post Message()
这个 API 真正神奇的地方在于 JavaScript 对象可以_自动转换_为 Objective-C 或 Swift 对象。
Handler 的名字可以通过 WKScript
协议中的 add
接口函数设置:
class Notification Script Message Handler: NSObject, WKScript Message Handler {
func user Content Controller(user Content Controller: WKUser Content Controller, did Receive Script Message message: WKScript Message!) {
println(message.body)
}
}
let user Content Controller = WKUser Content Controller()
let handler = Notification Script Message Handler()
user Content Controller.add Script Message Handler(handler, name: "notification")
于是当通知进入 app 的时候,比如说在页面中创建一个新对象,相关信息就可以这样传递:
window.webkit.message Handlers.notification.post Message({body: "..."});
添加用户脚本来对 web 事件监听并用 Message Handler 将信息传回 app。
同样的方法也可以用来收集页面信息用于 app 的页面展示或数据分析。
例如,如果某人要针对 NSHipster.com 做一个特别的浏览器,就可以加一个能够呼出相似文章列表的按钮:
// document.location.href == "https://nshipster.com/webkit"
function get Related Articles() {
var related = [];
var elements = document.get Element By Id("related").get Elements By Tag Name("a");
for (i = 0; i < elements.length; i++) {
var a = elements[i];
related.push({href: a.href, title: a.title});
}
window.webkit.message Handlers.related.post Message({articles: related});
}
let js = "get Related Articles();"
self.web View?.evaluate Java Script(js) { (_, error) in
println(error)
}
// Get results in previously-registered message handler
如果你的 app 只是对网页内容做了很简单的一层包装,那么 WKWeb
可以彻底改变这种状况。你对于性能和兼容性的所有愿望都将变为现实。所有你想要的东西都在这了。
如果你是一个原生纯粹主义者,你可能会被 iOS 8 新带来强大和扩展性功能吓到。有一个秘密就是,例如 Messages 这种原生应用都应用了 WebKit 来渲染复杂的页面元素。你可能尚且没有意识到,但事实是,webview 在移动开发最佳实践中应该得到一席之地。