笔记:iOS 与 JavaScript 的交互(三)WKWebView


 拍摄于 2017-04-15,第一天练科目二倒车入库,两小时下来腿已废。
拍摄于 2017-04-15,第一天练科目二倒车入库,两小时下来腿已废。

简介

WKWebView 开头的 WK 对应前端每天都在面对的 WebKit1。iOS 8 之后,官方更加提倡使用 WKWebview 来替代 UIWebView2,因为其在原生和网页交互时,给了原生更多的控制权限和数据交互方式,前者的例子是 HTML 中前端中基本的 alert()confirm() 都需要在原生适配了对应的 delegate 之后才能显示,后者对应 prompt() 提交数据也可以直接传送给原生。

新建和加载页面

## 网页加载进度的显示和 UIProgressView
录屏中每次刷新后,顶部通拦下那条闪现的绿色,就是加载进度,使用的是 UIProgressView

这一部分首先找到的是资料是 Paul Hudson 写的《Hacking with Swift》 中的第四章《Project 4: Easy Browser》,跟着做了一遍之后得到的效果和录屏的效果相去甚远:例子中的进度条显示并不是在顶部导航栏下有一条进度条,而是在底部的工具栏中。

要实现在顶部进度条(或者还有根据是否 isLoading 来显示「后退」按钮显示与否)的效果,大致的思路是手动建立 UIProgress 和作为前者容器的 Rect,将 UiProgress 放入 Rect 中,再把 Rect 置于导航栏之下(进度条的粗细通过设置自身的 height 得到)。

其次,进度条中进度的加载,主要概念是 key-value observing (KVO),对前端来说可以想象成 AJAX 上传时候用到的针对 ProgressEvent 的事件监听:

拦截、处理 JavaScript,UIDelegate

WKWebView 中,前端 window 对象里的 alert()confirm()prompt() 被前端调用是不会直接显示的,只有在 WKUIDelegate 中进行重写(UIDelegate 里除了三个方法的重写,代码有点长,直接看 demo 源文件里的代码,这里就简单贴一下各个 delegate method 和对应调用的 native 控件):

拦截、并处理 alert()

对应 native 里的 UIAlertControllerperferredStyle.alert

拦截、并处理 confirm()

对应 native 里的 UIAlertControllerperferredStyle 似乎 .alert.actionSheet 都可以。

拦截、并处理 prompt()

对应 native 里的 UIAlertControllerperferredStyle.alert

需要调用 addTextField(configurationHandler:) 方法来处理用户输入。

原生调用 JavaScript

和 UIWebView 中对 JavaSCript 的相关执行方法几乎一致,很直觉的 API,直接使用 evaluateJavaScript(_:completionHandler:)官方文档)来调用 JavaScript 文件中各个方法就可以。

调用的方法:
调用的方法:
调用的方法:

注入(injecting)一段 JavaScript,进行调用

作为更新和替代,WKWebView 提供了更加丰富的 API,并且对不同种类的 delegate 、方法做了分类。比如:上文提到使用 UIWebView 来注入一段脚本到当前加载的页面中,UIWebView 使用的方法是在 didFinishLoad 的时候使用直接执行脚本;而 WKWebView 则提供了 WKWebViewConfigurationWkPerference。同时,WKWebView 对于加载的时间的控制也可以在 userScript 设置不同的值,比如 injectionTime: .atDocumentEnd

在加载豆瓣的时候,通过 WKWebview 注入了上面提到的 user_script.jsuser_script.js 中包含两个方法,改变网页色的 changePageBackgroundColor() 和在首页四个按钮下加入一个新按钮的 addFakeSignalButton()

关于 WKWebView 的注入,首先需要知道:新建 WKViewView 的方法可以传入第二个参数: WKWebViewConfiguration 对象,这个对象又包含了 userContentController 属性,之后会在 JavaScript 调用中继续使用到这个对象和这个属性,这里先说一下注入脚本用到的:WKUserScriptaddUserScript(_:)

addUserScript(_:) 接收一个 WKUserScript 对象,本地加载页面的过程做一个简单的封装:

录屏中在豆瓣页面打开的时候的就已经变色、假按钮也已经加入,但在操作中还是需要确认网页已经完全加载完毕才能调用具体的方法。

这部分 demo(JavaScriptInvokeNativeViewController.swift)包含了 JavaScript 调用原生的方法,所以点击按钮会提示 「using window.webkit.messageHandlers.WKMessageSingal」, 具体的是用到了 WKWebview 的 WKScriptMessageHandler delegate,下面会提到。

JavaScript 调用原生,postMessage 以及 WKScriptMessageHandler

WKWebView 中 JavaScript 调用原生方法和 UIWebView 中的调用一致:原生和 JavaScript 相互之间做约定,不同的是相比 UIWebView 里约定 scheme 那种的方式,WKWebView 提供了单独为和 JavaScript 约定通信而设计的方法和 delegate:WKScriptMessageHandler官方文档3

「约定方法」和上文说道的「注入脚本」有些类似:通过在新建的时候在 WKWebView 中传入第二个参数 WKWebViewConfiguration,但参数的传递使用的是 add(_:name:) 官方文档 。整个约定的过程大致分为以下几个步骤:

  1. 原生(native)声明「暗号」;
  2. 原生(native)继续声明收到「暗号」、完成数据接收之后做什么;
  3. JavaScript 发起「暗号」;
  4. 原生(native)调用约定好使用的方法。
原生中进行约定的所有基本语句,在这里 WKMesesgaSignal 就是约定的「暗号」,实际上所做的是:当前 WKWebView 加载完毕了 HTML 之后,前端再熟悉不过的 window 对象会有多一个属性 WKMesesgaSignal 来让前端 postMesage()

不过到了这里还不能实现调用,还需要重写 WKScriptMessageHandler 中的 user​Content​Controller(_:​did​Receive:​) 官方文档 方法:

上面的例子里用 JavaScript 传递给 native 的是一串字符串,还可以直接发送 JSON,类似:

原生可以使用 Dictionary 来转换:

演示 Demo

Demo 中做了两步:

  1. 在 iOS 中加载的 html,input 输入内容后,点击按钮来调用系统组件(弹框);
  2. 通过右上角原生的 Clear Input 按钮来清除 html 中 input 里已经输入的内容。

一来一往使 iOS 和 JavaScript 完成了相互调用。

原生(native)声明「暗号(WKMesesgaSignal)」

原生(native)声明「暗号(WKMesesgaSignal)」

原生(native)继续声明收到「暗号」、完成数据接收之后做什么

这里一般需要在在 WKScriptMessageHandler delegate 中执行,类似网页中 DOM.ready()

原生(native)调用 JavaScript 清除 input 的值

调用 HTML 内的 JavaScript

JavaScript 将 input 值传递给原生,使用 WKWebViewConfiguration 对象和 WKScriptMessageHandler 方法。

参考


  1. Which powers Apple’s Safari web browser, It was forked by Google to create Blink ,引自 WebKit 的 维基
  2. In apps that run in iOS 8 and later, use the WKWebView class instead of using UIWebView ,引自《官方文档》。
  3. WKWebView 不能像 UIWebView 那样通过使用 valueForKeyPath() 方法来获取 JSContext 来配合 JavaScriptCore 使用,参考这里

感谢阅读

你们好, 2018 年初把小站从 Jekyll 迁移到 Hugo 的过程中,删除了评论区放的 Disqus 插件,考虑有二:首先无论评论、还是对笔记内容的进一步讨论,读者们更喜欢通过邮件、或者 Twitter 私信的方式来沟通;其次一年多以来 Disqus 后台能看到几乎都是垃圾留言(spam),所以这里直接贴一下邮件、以及 Twitter 账户 地址。

技术发展迭代很快,所以这些笔记内容也有类似新闻的时效性,不免有过时、或者错误的地方,欢迎指正 ^_^。

BEST
Lien(A.K.A 胡椒)
本站总访问量 本站总访客量 本文总阅读量