每个用过 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)! // 实例化 WKWebView self.webView = WKWebView(frame: CGRectZero) } override func viewDidLoad() { super.viewDidLoad() // 编程式加入 WKWebView view.addSubview(webView) view.insertSubview(webView, aboveSubview: progressView) webView.translatesAutoresizingMaskIntoConstraints = false let 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.canGoBack backButton.enabled = webView.canGoBack stopButton.image = webView.loading ? UIImage(name: "Cross") : UIImage(named: "Syncing") } else if keyPath == "title" { title = webView.title } else if keyPath == "estimatedProgress" { progressView.hidden = webView.estimatedProgress == 1 progressView.setProgress(Float(webView.estimatedProgress), animated: true) } } }
这些代码我觉得没什么好说的,除了WKWebView不能通过 IB 来可视化构建外,以上的代码最多是将 Autolayout 部分的代码优化一下就是了。写一写,做个 Example 就懂了。
与 Javascript 通信
通过WebKit就不需要通过 javascript 桥的方式来与DOM通信了。其实这也不是什么新技术,早再 windows98 在VB或者在Delphi中也可以通过COM接口用完全相类似的手法与DOM通信了。
废话不多说,讲讲 WebKit 的基本原理吧。以下是 WebKit Host 的Web进程 与 App 主进程的通信关系示意图:
这里包含两个过程
执行 javascript 脚本
我们可以将 javascript 脚本包含于 App 的 Bundle 内,作为应用程序资源。在运行期将其通过 WebKit 注入至目标网页内执行。
首先我们要准备一个目标网页,这里就以我自己的博客来做一个示例(http://ray.dotnetage.com)。在 App 中用WebKit打开是这样的
现在,我就将我博客上首页的大标题的文字改掉,具体的代码很简单:
$(".page-header h1").text("iOS注入测试");
然后,在 iOS项目内增加一个叫 inject.js 的脚本文件,将上述代码复制其内。
在 App 内包含的 javascript 脚本最好先在浏览器的控制台内执行一次,以确保脚本自身是可以被正确执行的。如果脚本中含有潜在错误,在App内是无法检测得到的。
然后,在控制器的构造函数内创建一个 WKWebViewConfiguration 实例,并作为参数传入 WKWebView的构造函数,具体代码如下:
// ViewController.swift import WebKit class 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的开始部分还是在文档的尾部。
再次执行代码,效果如下:
也就是说我们可以在 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 a method 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 the delegate 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" { // 这里就可以加入打开相册的代码了 } } }
从代码就可以看出原理的一二:
在构造 WKWebView 之前要用 addScriptMessageHandler 方法向配置对象注册一个消息名,这里的例程是 "openPhotoLibrary"。
实现 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);
这里就略过接口实现,直接看 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 SafariServices class 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
这确实是一项很不错的更新。
今天我注册了adMob,开始在我的应用程序中制作adMob广告。第一个是一个游戏(带有viewcontrollers),我想在游戏结束后在屏幕上显示一个间隙广告,所以我想在游戏结束后加载间隙广告(只有在游戏结束后才能访问此视图,并且游戏持续一分钟,所以它不应该出现得太频繁) 我看过很多教程,在youtube和admob网站上,我有很多问题,比如:即使我导入了它,也找不到admob框架,我在构建时禁
本文向大家介绍与Facebook有哪些惊人的事实?,包括了与Facebook有哪些惊人的事实?的使用技巧和注意事项,需要的朋友参考一下 截至2018年第一季度,Facebook每月有21.9亿活跃用户。在2012年第三季度,活跃的Facebook用户数量已超过10亿,成为有史以来第一个这样做的社交网络。活动用户是指最近30天内登录Facebook的用户。 2018年全球移动Facebook用户数量
问题内容: 这让我很精神。 我在Google Maps v3中使用了MarkerCluster,它在FF中可以正常工作,但是当我(和客户端)在Chrome或Safari中启动它时,群集就不存在了。 没有错误,只是不能在webkit中工作。 一些注意事项:它来自一些ajax加载的json,并且在jquery中。 该函数负责添加: 干杯! 重申一下,Chrome中没有错误,只是没有显示。 问题答案:
主要内容:令人惊艳的英文自我介绍【篇1】,令人惊艳的英文自我介绍【篇2】,令人惊艳的英文自我介绍【篇3】,令人惊艳的英文自我介绍【篇4】,令人惊艳的英文自我介绍【篇5】,令人惊艳的英文自我介绍【篇6】,令人惊艳的英文自我介绍【篇7】,令人惊艳的英文自我介绍(带翻译)7篇 令人惊艳的英文自我介绍(带翻译)的有哪些?“自我介绍”常用于礼貌问答的第一句,也是我们从小就要学会的一种沟通、交际的语言。下面小编给大家带来了令人惊艳的英文自我介绍(带翻译),供大家参考。 令人惊艳的英文自我介绍【篇1】 Goo
问题内容: 在我的应用程序中,我想在WKWebView中从 我的 域(例如:communionchapelefca.org)内打开链接,然后在Safari中打开来自 其他 域(例如:google.com)的链接。我希望以编程方式执行此操作。 我已经找到了一些关于Stack溢出的解决方案,但是它们似乎都是基于Obj-C的,我正在寻找使用Swift的解决方案。 ViewController.swift
问题内容: 我目前正在的应用程序中打开链接,但我正在寻找在 Safari中 打开链接的选项。 问题答案: 它不是“嵌入到Swift中”,但是您可以使用标准方法来完成。看一下UIApplication的 (已弃用) 和。 __ Swift 4 + Swift 5(iOS 10及更高版本) Swift 3(iOS 9及更低版本) 斯威夫特2.2