认识WebKit框架
对于简单的网页嵌入需求,使用UIWebView已经足够,但是对于更加复杂的需求,例如在网页与原生交互十分频繁的场景中,UIWebView处理起来就有些力不从心。iOS8之后,系统引入了WebKit框架,将应用中嵌入网页的相关接口提取整合成了一个完整的框架,WebKit框架中添加了一些原生与JavaScript交互的方法,并且WebKit框架中采用导航栈的模型进行网页的跳转管理,开发者可以更加容易地控制和管理网页的渲染。
WebKit框架中设计的类很多,框架的设计十分面向对象化和模块化,这也更有利于开发者代码的结构化。如图14-29中描述了WebKit框架中所涉及的重要的类和其相互之间的关系。
图14-29 WebKit框架结构图
WebKit框架中最核心的类应该属于WKWebView类,这个类专门用来进行网页视图的渲染,其需要通过WKWebViewConfiguration类来进行配置。
而WKWebViewConfiguration类中主要包含3个模块:WKPreferences用于进行自定义的偏好配置,WKProcesssPool类用于进程池的配置,WKUserContentController类也是一个核心类,其主要用来做native与JavaScript的交互管理。
关于WKUserContentController,其又需要两个类的协作,WKUserScript类用于通过原生代码向网页注入JavaScript代码,WKScriptMessageHandler类用于处理JavaScript代码调用的原生方法。除了前面提到的这些类外,开发者可以通过WKNavigationDelegate协议监听网页的活动,WKNavigationAction类是某个活动的实例化对象,
WKUIDelegate协议用于处理JavaScript代码中的一些弹出框事件,WKBackForwardList类用于栈结构的管理,WKBackForwardListItem类是每个网页节点的实例化对象。
使用WKWebViewConfiguration对网页视图进行配置
上一节整体向读者介绍了WebKit框架的结构和组成,整体分析清楚这个框架并不是一件容易的事。读者可以采取由点到线、由线到面的学习过程来掌握WebKit框架。首先WebKit框架中网页的展示和渲染是通过WKWebView来完成的,因此,可以从WKWebView入手学习,WKWebView的配置所依赖的类又是WKWebViewConfiguration类,因此最先要学习的是如何通过WKUserContentController对网页视图进行配置,并了解通过配置可以完成怎样的功能。使用Xcode开发工具创建一个命名为WebKitTwst的工程,要支持Http协议的网络请求,根据前面介绍的过程向Info.plist文件中添加相应键值。首先在ViewController.swift文件中引入WebKit框架,如下所示:
import WebKit
在ViewController类中添加如下代码,使界面加载出百度网站首页。
//声明WKWebView属性 var wkView:WKWebView? override func viewDidLoad() { super.viewDidLoad() //创建网页配置 let configuration = WKWebViewConfiguration() //对网页视图进行实例化 wkView = WKWebView(frame: self.view.frame, configuration: configuration) self.view.addSubview(wkView!) let url = URL(string: "http://www.baidu.com") let request = URLRequest(url: url!) wkView!.load(request) }
运行工程,可以看到使用WKWebView将百度首页加载了出来。可以使用WKProcessPool类为WKWebView实例配置一个进程池,示例如下:
//创建网页配置 let configuration = WKWebViewConfiguration() //配置进程池 let processPool = WKProcessPool() configuration.processPool = processPool
开发者一般不需要对进程池进行配置,配置为同一进程池中的WKWebView实例会共享一些资源,进程池在需要使用多个WKWebView实例时会使用到。对WKWebView实例进行自定义的偏好配置示例如下:
//偏好配置 let prefrence = WKPreferences() //设置网页界面的最小字号 prefrence.minimumFontSize = 0 //设置是否允许不经过用户交互由JavaScript代码自动打开窗口 prefrence.javaScriptCanOpenWindowsAutomatically = true configuration.preferences = prefrence //设置是否支持JavaScript脚本 默认为true configuration.defaultWebpagePreferences.allowsContentJavaScript = true
上面的示例代码中,可将WKPreferences类实例的javaScriptCanOpenWindowsAutomaticlly属性简单理解为是否允许自动弹出网页。
使用WKUserContentController类来进行原生与JavaScript交互相关配置,示例代码如下:
//进行原生与JavaScript交互配置 let userContentController = WKUserContentController() //设置代理并且注册要被javaScript代码调用的原生方法名称 userContentController.add(self, name: "nativeFunc") //向网页中注入一段javaScript代码 let javaScriptString = "function userFunc(){window.webkit.messageHandlers.nativeFunc.postMessage({\"班级\":\"珲少学堂\"})}; userFunc()" let userScript = WKUserScript(source: javaScriptString, injectionTime: .atDocumentStart, forMainFrameOnly: false) //进行注入 userContentController.addUserScript(userScript) configuration.userContentController = userContentController
WKUserContentController实例调用add(_: , name:)
方法用于设置JavaScript方法回调的代理和要监听的方法名。上面代码可以理解为,在WebKit中注册了nativeFunc方法的监听,当JavaScript代码对nativeFunc进行调用时,就会通知到WKScriptMessageHandler协议中约定的方法,开发者可进行后续的逻辑处理。
WKUserContentController实例调用addUserScript()
方法用于向网页中注入JavaScript代码,其中注入的JavaScript代码是封装入WKUserScript的对象,WKUserScript类的构造方法WKUserScript(source: , injecttTime: ,forMainFrameOnly:)
第1个参数为注入的JavaScript代码字符串,第2个参数设置注入的时机,第3个参数设置是否只在主界面注入,关于可注入时机定义在如下枚举中:
public enum WKUserScriptInjectionTime : Int { //在文档首部进行注入 case atDocumentStart = 0 //在文档尾部进行注入 case atDocumentEnd = 1 }
上面示例代码中注入的JavaScript代码如下:
function userFunc(){ window.webkit.messageHandlers.nativeFunc.postMessage({\"班级\":\"珲少学堂\"}) }; userFunc()
这段JavaScript代码的含义是创建了一个函数userFunc()
,函数中向nativeFunc方法发送了一个字典消息,字典中的键为“班级”,值为“珲少学堂”,之后调用了userFunc()
这个方法。因此读者可以理解为,这里手动向JavaScript注入了代码,代码的功能是与原生nativeFunc方法进行通信。
为ViewController类添加如下协议:
class ViewController: UIViewController, WKScriptMessageHandler
实现协议中的方法如下:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { print(message.body, message.name) }
当JavaScript代码向WebKit中注册的交互方法发送消息后,系统会调用这个协议方法,这个方法会传入一个WKScriptMessage类型的message参数,其中封装了传递的消息内容,WKScriptMessage类中提供的重要属性如下:
open class WKScriptMessage : NSObject { //消息体 对应上面传递的字典 open var body: Any { get } //此消息的网页视图来源 weak open var webView: WKWebView? { get } //传递消息的方法名 open var name: String { get } }
开发者也可以使用原生代码来调用网页中的JavaScript方法,使用WKWebView类中的如下方法:
public func evaluateJavaScript(_ javaScriptString: String, completionHandler:((AnyObject?, NSError? ) -> Swift.Void)? = nil)
WKWebView中重要属性和方法解析
WKWebView明显比UIWebView要强大许多,其中除了支持UIWebView中所有的方法外,还扩展添加了更多强大实用的功能。关于网页的前进和后退操作,WKWebView中提供了backForwardList属性,如下:
public var backForwordList: WKBackForwardList{ get }
WKBackForwardList类中存储了网页跳转中的各个网页节点,其中属性列举如下:
//当前的网页节点 var currentItem: WKBackForwardListItem? { get } //前进一个的网页节点 var backItem: WKBackForwardListItem? { get } //后退一个的网页节点 open var forwardItem: WKBackForwardListItem? { get } //某个索引对应的网页节点 open func item(at index: Int) -> WKBackForwardListItem? //所有后退的网页节点组成的数组 open var backList: [WKBackForwardListItem] { get } //所有前进的网页节点组成的数组 open var forwardList: [WKBackForwardListItem] { get }
通过WKBackForwardList类,开发者可以方便地获取到在整个网页跳转过程中缓存的任意一个网页节点。WKWebView类实例调用如下的方法可以跳转到任意一个网页节点:
public func go(to item: WKBackForwardListItem) -> WKNavigation?
WKWebKit类中还有一个十分重要的属性是estimatedProgress属性,这个属性是一个Double类型的值,其代表了网页加载的进度。在许多浏览器中,都会有一个网页加载进度条,用户可以通过进度条得知当前网页的加载状态。在实际开发中,配合UIProgressView进度条控件与KVO技术,就可以实现网页加载进度条功能。在ViewController类中添加UIProgressView属性如下:
var progressView:UIProgressView?
在ViewController类的viewDidLoad()
方法中添加如下代码:
//创建进度条控件 progressView = UIProgressView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 10)) progressView? .progressTintColor = UIColor.green progressView? .progress = 0 self.view.addSubview(progressView!) //对WKWebView实例的estimatedProgress属性进行监听 wkView? .addObserver(self, forKeyPath: "estimatedProgress", options: NSKeyValueObservingOptions.new, context: nil) //在ViewController类中实现如下方法 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "estimatiedProgress" { progressView? .progress = Float(wkView! .estimatedProgress) } }
KVO全称Key-Value-Observe,即键值监听。是iOS开发中一种常用的属性监听技术,其通过设置监听者来监听某个类的某个属性,当属性值发生改变时就会通知监听者,在回调的通知方法中,开发者可以获取到所监听的属性值的相关信息。
关于WKUIDelegate协议
WKUIDelegate协议也是处理JavaScript与原生代码交互的一个重要协议,其中主要定义了一些与网页警告框有关的回调方法。重要方法列举如下:
//当网页视图被创建时会调用 optional func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? //当网页视图被关闭时会被调用 optional func webViewDidClose(_ webView: WKWebView) //JavaScript代码弹出alert()警告框时会调用的原生方法 开发者处理完成逻辑后,需要调用completionHandler()闭包进行返回 optional func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) //JavaScript代码弹出confirm()确认框的时候会调用的原生方法 开发者处理完成逻辑后,需要调用completionHandler()闭包,这个闭包中需要传入一个Bool类型的参数将用户选择的结果返回 optional func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) // JavaScript的prompt事件回调的原生方法prompt会在网页中弹出输入框,开发者处理完交互逻辑后,需要调用completionHandler()闭包将用户输入的信息回传 optional func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void)
WebKit框架是iOS开发框架中一个比较复杂的框架,也是难点与进阶点。读者在学习的时候,注意整体把握,在实战练习中多多理解体会。
摘自《Swift从入门到精通》