WKWebView 原生与JavaScript交互方法

简介: WKWebView 原生与JavaScript交互方法

认识WebKit框架



对于简单的网页嵌入需求,使用UIWebView已经足够,但是对于更加复杂的需求,例如在网页与原生交互十分频繁的场景中,UIWebView处理起来就有些力不从心。iOS8之后,系统引入了WebKit框架,将应用中嵌入网页的相关接口提取整合成了一个完整的框架,WebKit框架中添加了一些原生与JavaScript交互的方法,并且WebKit框架中采用导航栈的模型进行网页的跳转管理,开发者可以更加容易地控制和管理网页的渲染。


WebKit框架中设计的类很多,框架的设计十分面向对象化和模块化,这也更有利于开发者代码的结构化。如图14-29中描述了WebKit框架中所涉及的重要的类和其相互之间的关系。


2466108-de2e7603b85af1fb.webp.jpg


图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从入门到精通》


目录
相关文章
|
1月前
|
JavaScript 前端开发 程序员
前端原生Js批量修改页面元素属性的2个方法
原生 Js 的 getElementsByClassName 和 querySelectorAll 都能获取批量的页面元素,但是它们之间有些细微的差别,稍不注意,就很容易弄错!
|
1月前
|
Web App开发 JavaScript 前端开发
如何确保 Math 对象的方法在不同的 JavaScript 环境中具有一致的精度?
【10月更文挑战第29天】通过遵循标准和最佳实践、采用固定精度计算、进行全面的测试与验证、避免隐式类型转换以及持续关注和更新等方法,可以在很大程度上确保Math对象的方法在不同的JavaScript环境中具有一致的精度,从而提高代码的可靠性和可移植性。
|
28天前
|
监控 JavaScript Java
Node.js中内存泄漏的检测方法
检测内存泄漏需要综合运用多种方法,并结合实际的应用场景和代码特点进行分析。及时发现和解决内存泄漏问题,可以提高应用的稳定性和性能,避免潜在的风险和故障。同时,不断学习和掌握内存管理的知识,也是有效预防内存泄漏的重要途径。
122 52
|
1月前
|
JavaScript 前端开发 索引
js中DOM的基础方法
【10月更文挑战第31天】这些DOM基础方法是操作网页文档结构和实现交互效果的重要工具,通过它们可以动态地改变页面的内容、样式和行为,为用户提供丰富的交互体验。
|
1月前
|
缓存 JavaScript UED
js中BOM中的方法
【10月更文挑战第31天】
|
29天前
|
缓存 JavaScript 前端开发
JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用
本文深入讲解了 JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用。
42 5
|
1月前
|
JSON 前端开发 JavaScript
聊聊 Go 语言中的 JSON 序列化与 js 前端交互类型失真问题
在Web开发中,后端与前端的数据交换常使用JSON格式,但JavaScript的数字类型仅能安全处理-2^53到2^53间的整数,超出此范围会导致精度丢失。本文通过Go语言的`encoding/json`包,介绍如何通过将大整数以字符串形式序列化和反序列化,有效解决这一问题,确保前后端数据交换的准确性。
36 4
|
1月前
|
JavaScript 前端开发
js中的bind,call,apply方法的区别以及用法
JavaScript中,`bind`、`call`和`apply`均可改变函数的`this`指向并传递参数。其中,`bind`返回一个新函数,不立即执行;`call`和`apply`则立即执行,且`apply`的参数以数组形式传递。三者在改变`this`指向及传参上功能相似,但在执行时机和参数传递方式上有所区别。
26 1
|
1月前
|
JavaScript 前端开发
.js方法参数argument
【10月更文挑战第26天】`arguments` 对象为JavaScript函数提供了一种灵活处理参数的方式,能够满足各种不同的参数传递和处理需求,在实际开发中具有广泛的应用价值。
41 7
|
1月前
|
移动开发 前端开发 JavaScript
前端实训,刚入门,我用原生技术(H5、C3、JS、JQ)手写【网易游戏】页面特效
于辰在大学期间带领团队参考网易游戏官网的部分游戏页面,开发了一系列前端实训作品。项目包括首页、2021校园招聘页面和明日之后游戏页面,涉及多种特效实现,如动态图片切换和人物聚合效果。作品源码已上传至CSDN,视频效果可在CSDN预览。
41 0
前端实训,刚入门,我用原生技术(H5、C3、JS、JQ)手写【网易游戏】页面特效