谈谈iOS9中的WebKit 与 Safari

2023-10-23 21:10
文章标签 谈谈 webkit safari ios9

本文主要是介绍谈谈iOS9中的WebKit 与 Safari,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

每个用过 UIWebView 的iOS开发者对其诸多的限制和有限的功能也深有感触。悻然,自iOS8推出 WebKit 框架后将改变这一窘境。在本文我将会深入WebKit来体验一下它给我们带来的好处,同时也看看在iOS9中新加入的 SFSafariViewController 有些什么新的惊喜。

通用的浏览行为

所谓的通用浏览行为主要可以归纳为以下的几种:

  • 网页载入进度
  • 前进
  • 后退
  • 刷新

如果每个用到 WebView 的 app都要做一个专用的Controller也挺麻烦的,我以前就直接采用其它第三方写好的包来完成。

但现在,如果用 WKWebView 将变得很方便,以代码说话吧:

class ViewController: UIViewController  {var webView: WKWebView!@IBOutlet weak var progressView: UIProgressView!required init(coder aDecoder: NSCoder) {super.init(coder: aDecoder)!// 实例化 WKWebViewself.webView = WKWebView(frame: CGRectZero)}override func viewDidLoad() {super.viewDidLoad()// 编程式加入 WKWebViewview.addSubview(webView)view.insertSubview(webView, aboveSubview: progressView)webView.translatesAutoresizingMaskIntoConstraints = falselet widthConstraint = NSLayoutConstraint(item:webView, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1 , constant: 0)view.addConstraint(widthConstraint)let heightConstraint = NSLayoutConstraint(item:webView,attribute: .Height, relatedBy: .Equal,toItem: view, attribute: .Height, multiplier:1, constant: -46)view.addConstraint(heightConstraint)// 检测webView对象属性的变化webView.addObserver(self, forKeyPath: "loading", options: .New, context: nil)webView.addObserver(self, forKeyPath: "title", options: .New, context: nil)//加载网页let request = NSURLRequest(URL: NSURL(string: "http://ray.dotnetage.com")!)webView.loadRequest(request)}override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {if (keyPath == "loading") {// 检测按钮的可用性forwardButton.enabled = webView.canGoBackbackButton.enabled = webView.canGoBackstopButton.image = webView.loading ? UIImage(name: "Cross") : UIImage(named: "Syncing")} else if keyPath == "title" {title = webView.title} else if keyPath == "estimatedProgress" {progressView.hidden = webView.estimatedProgress == 1progressView.setProgress(Float(webView.estimatedProgress), animated: true)}}
}

这些代码我觉得没什么好说的,除了WKWebView不能通过 IB 来可视化构建外,以上的代码最多是将 Autolayout 部分的代码优化一下就是了。写一写,做个 Example 就懂了。

与 Javascript 通信

通过WebKit就不需要通过 javascript 桥的方式来与DOM通信了。其实这也不是什么新技术,早再 windows98 在VB或者在Delphi中也可以通过COM接口用完全相类似的手法与DOM通信了。

废话不多说,讲讲 WebKit 的基本原理吧。以下是 WebKit Host 的Web进程 与 App 主进程的通信关系示意图:

overview.png

这里包含两个过程

执行 javascript 脚本

我们可以将 javascript 脚本包含于 App 的 Bundle 内,作为应用程序资源。在运行期将其通过 WebKit 注入至目标网页内执行。

首先我们要准备一个目标网页,这里就以我自己的博客来做一个示例(http://ray.dotnetage.com)。在 App 中用WebKit打开是这样的

645016-20151113114448181-861226265.png

现在,我就将我博客上首页的大标题的文字改掉,具体的代码很简单:

$(".page-header h1").text("iOS注入测试");

然后,在 iOS项目内增加一个叫 inject.js 的脚本文件,将上述代码复制其内。

在 App 内包含的 javascript 脚本最好先在浏览器的控制台内执行一次,以确保脚本自身是可以被正确执行的。如果脚本中含有潜在错误,在App内是无法检测得到的。

然后,在控制器的构造函数内创建一个 WKWebViewConfiguration 实例,并作为参数传入 WKWebView的构造函数,具体代码如下:

// ViewController.swiftimport WebKitclass ViewController : UIViewController {var webView: WKWebView!required init(coder aDecoder: NSCoder) {super.init(coder: aDecoder)!let configuation = WKWebViewConfiguration()configuation.userContentController.addUserScript(getUserScript("inject"))self.webView = WKWebView(frame: CGRectZero,configuration: configuation)}// 从资源中读取 javascript 脚本func getUserScript(fromName: String)-> WKUserScript {let filePath = NSBundle.mainBundle().pathForResource(fromName, ofType: "js")let js = try! String(contentsOfFile: filePath!, encoding: NSUTF8StringEncoding)return WKUserScript(source: js, injectionTime: .AtDocumentEnd, forMainFrameOnly: true)}...
}

此代码段中需要注意的另一点是在自定义方法 getUserScript() 所返回的 WKUserScript 对象。我们可以通过 injectionTime 决定将脚本注入至HTML的开始部分还是在文档的尾部。

再次执行代码,效果如下:

645016-20151113114507165-576391589.png

也就是说我们可以在 app 内通过 WebKit 注入javascript后就可以任意地操控页面内的所有 DOM 对象!

javascript 的回调

除了从 app 一端将代码注入到浏览器,执行一个动作。某些情况下我们还需要从网页上做某一些处理后,例如将网页内的某些元素读出并转为一个 json 对象集合,回传给 App 处理。又或者我们的 app 在加载一个网页之后想一次性地读出页面内的所有图像,当用户点击这些图像的时候我们用 app 的本地方式来全屏预览,诸如此类。在这些语境下:

我们都得从网页内返回对象

也就是说,在网页的进程内要向 app 进程通信,那么我们就需要在脚本中使用:

webkit.messageHandlers.{MessageName}.postMessage([params]);

这个方法在标准的HTML5浏览器是不能直接执行的,例如 Chrome和 Safair。只有通过 WebKit Host 的页面才会出现这个 webkit 对象。 这并不难理解,只是 WebKit 在加载页面后向 windows 注入了 webkit 这个实例,使得 javascript 可以通过它来向 app 发送信息。

如果我们要向 app 发送一个信息,例如:在页面上的一个按钮被点击后,执行 app 内打开相册的代码,那么就得先在 javascript 上写好这样的代码:

$("#mybutton").click(function(){webkit.messageHandlers.openPhotoLibrary.postMessage();
});

请留意 openPhotoLibrary 这个对象在Swift是没有,当这个方法被回传到 Swift 的时候这只是一个消息的名字,而在Swift中要接收这种来至于浏览器发送的信息我们的控制器就需要实现 WKScriptMessageHandler 这个接口,它只有一个方法,我们多花些篇幅直接将这个接口的代码打开:

/*! A class conforming to the WKScriptMessageHandler protocol provides amethod for receiving messages from JavaScript running in a webpage.*/
public protocol WKScriptMessageHandler : NSObjectProtocol {/*! @abstract Invoked when a script message is received from a webpage.@param userContentController The user content controller invoking thedelegate method.@param message The script message received.*/@available(iOS 8.0, *)public func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage)
}

那么,我们就直接实现这个接口:

class ViewController: UIViewController, WKScriptMessageHandler {required init(coder aDecoder: NSCoder) {// ... 之前的代码同上configuation.userContentController.addScriptMessageHandler(self, name: "openPhotoLibrary")self.webView = WKWebView(frame: CGRectZero,configuration: configuation)}...func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {if message.name == "openPhotoLibrary" {// 这里就可以加入打开相册的代码了}}
}

从代码就可以看出原理的一二:

  1. 在构造 WKWebView 之前要用 addScriptMessageHandler 方法向配置对象注册一个消息名,这里的例程是 "openPhotoLibrary"。
  2. 实现 WKScriptMessageHandler 接口,从 userContentController() 方法的 message.name 参数中判断消息的源头,执行对应的代码。

另外,如果我们需要从javascript脚本中向 app 传入对象,可以直接在 postMessage() 方法内将对象作为参数输入,但通常这个参数的类型应该是一个数组或者是普通的JSON对象,这样在 app 才能用字典对象将其从新读出。

例如,我从当前网页中将所有的菜单的地址和名称读出,并生成了一个 menus 的 javascript 数组对象:


var menus = $(".navbar a").map(function(n,i){return {title: $(n).text,link: $(n).attr("href")};
});webkit.messageHandlers.didFetchMenus.postMessage(menus);

在 Swift 中定义一个 Menu 类:

class Menu {var title =  ""var link =  ""init(dict : Dictionary<String,String>) {self.title = dict["title"]!self.link = dict["link"]!}
}

这里就略过接口实现,直接看 userContentController 方法实现:


var menus: [Menus]?func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {if message.name == "didFetchMenus" {if let resultArray = message.body as? [Dictionary<String,String>] {menus = resultArray.map{ Menu(dict: $0) }// 这里就取出并将JSON转换为 Swift 的Menu对象了print(menus)}}
}

iOS9 中的 Safair 浏览器

在 iOS9 中加入了 SafariServices 这个新的模块,其作用就是提供了一个全功能的内嵌式 Safair,通过
SFSafariViewController 就能像普通的 控制器那样使用。

以下是一个简单的例子

import UIKit
import SafariServicesclass ViewController: UIViewController {@IBAction func openBrowser(sender: AnyObject) {let safari = SFSafariViewController(URL:NSURL(string:"http://www.apple.com")!)self.showViewController(safari, sender: self)}
}

SFSafariViewController 和 WebKit 的最大区别是 SFSafariViewController 没有什么可控制方法,只是一个可以完全嵌入到 app 中的一个控制器,避免了像以前那样如果打开一个外部链接要跳出当前的app,而且 SFSafariViewController 嵌入的 Safari 和 Safari 内的所有功能是一样的,同样支持 3D Touch 和切页的等特色功能。且当我们的 app 采用外部网络帐号进行集成登录时,Safari 能更直接获取到当前 app的应用上下文,而无须再跳出重新在外部登入后再返回至App。这无疑是大大地增强了 app 在与 Safari 集成的时的使用体验。

在 Apple 的开发者网站上对 WebKit 与 SafariServices 的选择上给出了这样的意见:

  • 如果需要与网页交互则选择 WebKit
  • 如果需要与Safari具有同样的使用体验且不需要与网页交互推荐使用 SafariServices

这确实是一项很不错的更新。

转载于:https://www.cnblogs.com/Ray-liang/p/4961702.html

这篇关于谈谈iOS9中的WebKit 与 Safari的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/270457

相关文章

Apple quietly slips WebRTC audio, video into Safari's WebKit spec

转自:http://www.zdnet.com/article/apple-quietly-slips-webrtc-audio-video-into-safaris-webkit-spec/?from=timeline&isappinstalled=0 http://www.zdnet.com/article/apple-quietly-slips-webrtc-audio-video-

谈谈我的8年编程自学辛酸史

2008年:第一个脚印 2008年的一个周末,我一如既往读着最爱的《电脑迷》和《电脑爱好者》,不经意间看见一篇文章,教读者如何自己制作一个exe来说生日快乐,于是,或许是我一生的道路就从这里开始了。 当时已经痴迷于电脑软件,但是身为初中生的我并没有机会玩电脑,只能苦苦地看着杂志记录好玩的软件,并没有想过真的要自己去制作软件。直到去新华书店买辅导书的一次契机,我看见了一本《Java语言教程》

作为面试官的一点点感悟,谈谈技术人的成长之路

因为工作上的原因,做过几次面试官,面试的同学有应届生,也有工作3-5年的老技术人。最近也频繁作为面试官帮助筛选候选人,中间有很多值得深思的东西,我记录了下来分享给大家。 以下观点仅为个人观点,不代表任何公司的立场。        01 面试不是简单的你问我答 一般来讲,作为面试官和候选人进行沟通的第一个问题是一般是自我介绍,整个自我介绍的情况应该控制在2分钟左右,阐述自己的教育背景,项目经历

谈谈经典限流方法—漏桶、令牌桶与Guava RateLimiter的实现

大数据技术与架构 点击右侧关注,大数据开发领域最强公众号! 暴走大数据 点击右侧关注,暴走大数据! 高并发的业务系统经常要接受大流量的考验,为了保证系统的响应度和稳定性,往往都需要对有风险的接口实施限流(rate limiting),更高大上的说法则是“流量整形”(traffic shaping)。限流的思想最初来源于计算机网络,有两种经典的方法:漏桶和令牌桶。本文先来稍微研究一下它们。

uni-app 扫码优化:谈谈我是如何提升安卓 App 扫码准确率的

一. 前言 之前的一个项目遭到用户吐槽:“你们这个 App 扫码的正确率太低了,尤其是安卓的设备。经常性的扫码扫不出来,就算是扫出来了,也是错误的结果!” 由于之前是扫描二维码的需求,所以没有对扫描条形码做严格的测试,客户提示说是条形码扫描效率低下。随即,我用自己的手机测试了一下,在安卓手机上确实有这样的问题,扫码准确率确实是低,尤其是条形码,扫码效率慢且不准确。扫描二维码的的效率还算可以

谈谈singelton单例模式

 单例模式是一种常用设计模式。该类只有一个实例,而且该类自行创建实例。         很多时候,服务器都只需要一个全局对象,这样方便协调系统的整体行为。比如系统的配置文件,系统只需要一个单例对象读取加载,其他对象只需要通过该单例对象获取配置信息。这样方便在复杂模式下对系统配置的管理。          java中常用单例模式: public class Singleton(

谈谈函数返回值为什么不能重载

一、函数的定义:       函数将有效的输入值变换为唯一的输出值,同一输入总是对应同一输出。       计算机本质是对抽象数学公式的具体实现,并以此具体实现来解决现实生活中的实际问题。 注:wiki百科对 “函数” 的定义如图,图比较大,请点击打开详情,左右拖动查看 全部内容。 二、悖论      反过来设想一下,如果返回值的类型 能用来 重载,那么对于相同的输入值,程序怎么决定

谈谈分组:sql的group by+聚集函数 和 python的groupby+agg

直接举例子+分析例子+总结来说,我先给几个表: 学生表:student(学号,姓名,年龄,院系); 课程表:course(课程号,课程名,学分); 学生选课表:sc(学号,课程号,分数); 啥时候用分组呢? 我由简至深来谈。 1、比如让我们查询各个课程号及相应的选课人数。 首先定位到sc表上,“各个”很明显就是要按课程分组,group by出场了,分组后对每组去统计选课人数,聚集函数出场了。

谈谈你对 Java 平台的理解?

谈谈你对 Java 平台的理解? 先谈谈我的理解: 一次编译,永久执行。依赖于jre的运行环境,jdk提供核心类库,java源代码经过javac编译为.class文件,.class文件经过JVM解析或编译运行,无需过多关心不同操作系统的差异性。 典型回答 Java 本身是一种面向对象的语言,最显著的特性有两个方面,一是所谓的“书写一次,到处运行”(Write once, run any

谈谈最近一段时间的工作

被原来的公司派出去做一个天津的项目,一个多月了,平均每天加班加到10点左右,周末都不休息,感觉好累啊。 最累的是心了,原来辛苦付出的四个月不是很被认可, 我是实习生,但是我不想一直做实习生, 这个月收获很多,把自己以前不敢尝试的后台狠狠的做了一次, 希望以后的日子自己能够越来越厉害! 近期打算辞职,过年以后向着北京出发!