作者:GABRIEL THEODOROPOULOS,时间:2016/4/12
翻译:BigNerdCoding, 如有错误欢迎指出。 原文链接
在iOS SDK中你能找到一些不知名但是却非常有用的框架。其中的大多数都能给开发者带来便利节约开发时间。 Quick Look Framework 就是这些不知名的框架中的一个,即使你之前没有使用过该框架,你也能从框架的名字中了解一二。该框架为app提供文件的预览功能。和大多数人一样,我也是在第一次需要使用到该框架的时候才了解它,然后就喜欢上了。
Quick Look Framework使用起来很方便,并且该框架支持下面类型的文档:
iWork 文档(Pages、Numbers、Keynote)
Microsoft Office documents(Office 97版本以及更新的版本)
PDF 文件
Images
Text
Rich-Text Format documents(富文本格式文档)
Comma-Separated Value files (csv)
正如你现在可能会想到的一样,如果你的app需要处理上面类型的文档并且允许用户对文档进行预览的话那么这个框架能够帮你解决这些问题。除此之外,该框架还提供了分享功能。一个 UIActivityViewController 会被用来发送和分享预览的文档。
更具体来说,在使用Quick Look框架的时候开发者的主要任务就是为预览视图控制器提供 datasource ,以便视图控制器打开文档。该datasource事实上是一个指定文件路径 NSURL 对象的列表,当然文档可以是本地也可以在远程。本地文件包括那些文件目录中的文档或者该应用程序 bundle 中的文档。
该框架提供一个名为 QLPreviewController (Quick Look Preview Controller)的视图控制器来预览文档。该视图控制器既可以作为模态控制器,也可以push到navigation栈中。除了这个主要的构成,该框架里面还附带了分享的功能,以及一个展示可用文件的列表视图这样你就可以在查看这个视图的时候不离开该视图控制器。另外,该框架要求你必须实现 QLPreviewControllerDataSource 协议中的两个方法,这样才能够正确的预览文件。除了这个协议之外还有另一个名为 QLPreviewControllerDelegate 的协议,你可以选择性的实现该协议里面的一些方法来对视图控制器做出控制。
整如我们的一贯风格,我们会对上面的细节进行讨论,并且会在过程中使用一个Demo。在快速查看框架之前我们还是先来了解一些Demo。
基于本文的需求,我们将会创建一个只有一个视图控制器的 navigation-based app。该视图控制器会使用一个tableview来展示app中的文件。我们不会演示所有支持的文件类型,而是从中挑选一部分。但是这主以让我们明白该框架是如何工作的。
当我们点击了tableview中代表文件的某一行之后, Quick Look Preview Controller 就会打开对应的文档进行预览。该预览视图控制器会被压到navigation栈中,但是我还是会给你讲解如何模态的展示该视图控制器(事实上这与你模态展示其他视图的方法是一样的)。并且当该预览界面打开的时候我们还能看见一些预览视图控制器提供的功能(分享和文件选择),当然还有一些可选的delegate方法。
下面的截图就是 FileListViewController 视图的初始界面。正如图片中显示的一样文件的名称和类型都展示在每一行中。
当文件打开的时候,预览界面如下:
你可以在阅读余下部分之前下载起始 工程 。当代码下载完成后,你可以打开浏览一下代码,我们将在下面一部分一步步的实现功能。
在起始工程文件里面,你能找到一系列我们将在demo中进行打开预览的文档。虽然文件已经存在,但是想正确的进行打开预览的话还不足够。我们需要让预览视图控制器知道需要打开的文件是哪一个。
具体来说,我们会创建一个数组该数组的内容是演示文件的文件名。其中的文件名的有下面两个目的:
在tableview中正确的显示文件名和描述
更重要的一点是,我们会创建一个 NSURL 对象的列作为框架的 datasource ,这样就能正确的取出文件进行预览了。
现在我们已经很清楚每一个文件的作用了,所有我们现在需要动起手来了。首先,我们创建一个文件名数组(文件拓展名也需要包含在内)。确保 FileListViewController.swift 文件存在并打开它,在文件对应的类中添加下面的代码:
let fileNames = ["AppCoda-PDF.pdf", "AppCoda-Pages.pages", "AppCoda-Word.docx", "AppCoda-Keynote.key", "AppCoda-Text.txt", "AppCoda-Image.jpeg"]
在我们的demo中因为那些需要预览的文件名是已知的,所有我们可以很容易的声明 fileNames 数组并进行赋值。但是在真实的应用中,你提前就知道文件名的可行性微乎其微(例如:文件是下载得到的)。在这种情况下,你需要声明一个数组变量(不是上面的常量)来动态的田间一个或者多个文件到里面。
现在我们再来做出一个声明:
var fileURLs = [NSURL]()
该数组不尽会作为Quick Look framework的datasource也会作为tableview的datasource。
接下来我们会创建一个函数并把所有的相关值添加到刚刚声明的变量里面。在该函数里面,我们会便利 fileNames 数组中的每一个文件名,并创建对应的 NSURL 对象,当我们确定对应的文件存在的时候,我们就会把该对象添加到数组里面,这就是教程这部分的主要任务:
func prepareFileURLs() { for file in fileNames { let fileParts = file.componentsSeparatedByString(".") if let fileURL = NSBundle.mainBundle().URLForResource(fileParts[0], withExtension: fileParts[1]) { if NSFileManager.defaultManager().fileExistsAtPath(fileURL.path!) { fileURLs.append(fileURL) } } } }
主要到我们使用了 String 类型的 componentsSeparatedByString(...) 方法来将每一个文件名分为名字以及拓展名。剩余的部分久很简单了:我们使用 NSBundle 类的 URLForResource(...) 方法来创建 NSURL 对象,然后在确定新创建的 NSURL 对象存在的时候,我们就将该对象添加到 fileURLs 数组里面。
我们在 viewDidLoad() 方法里面调用上面的方法:
override func viewDidLoad() { ... prepareFileURLs() }
在tableview拥有了数据源的时候,我们就可以展示对应的可能被预览的文件和文件类型描述。需要注意的是该部分只与我们的demo相关与Quick Look framework并没有关系,但是之所以我在教程中故意添加这一部分是为了让你在自己的工程中做个参照。
我们想要达到的效果如下:
我们的目标是从每一个文件URL描述的 NSURL 对象开始,然后进行正确的处理最后在tableview中显示出文件名称和对应的文件类型。为了实现这一目标,我们需要将 NSURL 转化为 String 类型并将它分为两个部分,文件名称和对应的拓展类型。
接下来我们要马上要讨论的就是创建一个新的函数来实现该目的。虽然下面的代码片段并不是很难,我还是会在每一行代码上面添加注释以便你能更好的理解。需要注意的是该函数并不是返回一个单一的值而是一个元组(tuple),第一个值是文件真实的名称而第二个是文件的拓展类型。
func extractAndBreakFilenameInComponents(fileURL: NSURL) -> (fileName: String, fileExtension: String) { // Break the NSURL path into its components and create a new array with those components. let fileURLParts = fileURL.path!.componentsSeparatedByString("/") // Get the file name from the last position of the array above. let fileName = fileURLParts.last // Break the file name into its components based on the period symbol ("."). let filenameParts = fileName?.componentsSeparatedByString(".") // Return a tuple. return (filenameParts![0], filenameParts![1]) }
上面的方法将会成为一个非常有用的工具,因为该函数实现了两个目的:
在tablevie中我们将会展示单一的文件名(第一个返回值)。
我们会使用第二个返回值对应的文件类型来创建一个简短的文件描述。
现在实现第一个,我们找到 tableView(tableView:cellForRowAtIndexPath) 方法。将它修改为下面所示的一样(在cell dequeueing和返回之间添加两行代码),这样你就能将每一个文件名称部分作为cell的main text label并进行展示:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("idCellFile", forIndexPath: indexPath) let currentFileParts = extractAndBreakFilenameInComponents(fileURLs[indexPath.row]) cell.textLabel?.text = currentFileParts.fileName return cell }
现在我们创建另一个方法。该函数将会返回对应文件类型的简单类型描述。正如你看见的那样,这部分并不是很难,我们使用了一个 switch 语句来决定应该返回声明描述。这里我们只会判断demo中使用的文件类型,你可以根据自己的需求进行删减。
func getFileTypeFromFileExtension(fileExtension: String) -> String { var fileType = "" switch fileExtension { case "docx": fileType = "Microsoft Word document" case "pages": fileType = "Pages document" case "jpeg": fileType = "Image document" case "key": fileType = "Keynote document" case "pdf": fileType = "PDF document" default: fileType = "Text document" } return fileType }
回到 tableView(tableView:cellForRowAtIndexPath) 中,我们添加一行代码,代码中会调用上面的函数并将返回值作为secondary label。
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("idCellFile", forIndexPath: indexPath) ... cell.detailTextLabel?.text = getFileTypeFromFileExtension(currentFileParts.fileExtension) return cell }
还剩最后一件事等着我们去完成,那就是设置tableview中正确的行数。如果你注意到的话,起始工程中默认是返回0,如果你想看见那些行的话需要进行修改。修改的代码如下所示:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return fileURLs.count }
一起就绪你可以运行demo了,如果你是按照文章中的步骤一步步来做的话,你就能可见文章中本部分开始的截图的画面。
当计划使用Quick Look framework的时候,第一步当然就是导入这个类了。这也是我们这部分内容的起点。打开 FileListViewController.swift 文件该类声明下面导入框架:
import QuickLook
现在我们准备声明并同时初始化一个重要的对象以便我们使用Quick Look framework的特性。在 FileListViewController 类里面添加如下声明:
let quickLookController = QLPreviewController()
正如你看见的那样,我在这里声明了一个 QLPreviewController 类对象,然而这不是强制性的,如果你不想你也可以不这样做。你可以使用一个替代的方法,那就是在需要使用到框架的函数里面使用局部变量。
在使用 quickLookController 对象之前,我们需要遵守 QLPreviewControllerDataSource 协议。这一步是使用Quick Look framework时候的强制步骤,因为接下来你将看到的方法必须设置Quick Look Preview Controller的datasource。然而,在查看这些方法之前,我们回到类的第一行添加协议 QLPreviewControllerDataSource :
class FileListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, QLPreviewControllerDataSource
现在我们来看看两个必须实现的datasource方法。正如我在前面提到的,这两个方法让Quick Look Preview Controller能够实现预览,因为它们告诉了视图控制器总共有多少个文件需要预览以及当前预览的文件是哪一个。很明显,我们前面创建的 fileURLs 数组再次发挥作用了。
第一个方法就是确定Quick Look Preview Controller中总共有多少个文件需要预览。这个数字就是 fileURLs 数组里的文件数目,代码如下:
func numberOfPreviewItemsInPreviewController(controller: QLPreviewController) -> Int { return fileURLs.count }
第二个方法就是确定 fileURLs 数组里面那一个文件将会被预览:
func previewController(controller: QLPreviewController, previewItemAtIndex index: Int) -> QLPreviewItem { return fileURLs[index] }
根据官方的文档,上面方法(准确来说是协议)返回的 QLPreviewItem 对象实践上是NSURL类的category,所以我们直接返回NSURL对象没有任何问题(简单来说这两者几乎没有任何区别)。你可能觉得 QLPreviewItem 协议的方法很有用,你可以查看官方 文档 ,以便你能创建一个自定义的 QLPreviewItem 对象。
无论什么时候你需要使用 QLPreviewControllerDataSource ,你都需要实现上面两个方法。一定要确保
Quick Look Preview Controller中的文档数量(也就是第一个函数的返回值)一定与作为datasource的集合里面的数量一致,否则的话就可能出现数组访问越界的错误。
当然还有最后一步不能忘记了。我们不需将我们的类设置为 quickLookController 对象的datasource。找到 viewDidLoad() 函数,并添加如下代码:
override func viewDidLoad() { ... quickLookController.dataSource = self }
在实现了上面两个函数并成功设置对象的datasource之后,现在我们需要在demo中实现tap点击并打开用户选择的文档进行预览了。为了这一目标我们实现如下的函数:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { }
一个最基本原则我们一定要牢记,那就是我们必须确保我们试图预览的这个文件类型是被Quick Look framework所支持的。当然这并不难,因为 QLPreviewController 类提供了一个名为 canPreviewItem(:) 的方法来处理这个问题。如果能够对文件进行预览的话,那么该方法就会返回一个 true ,相反则会返回 false 。不必多说我们只会预览那些返回值为 true 的文档。
如果你像上面描述的那样做了检查并且框架也支持,接下来我们就要确定当前点击对象的 index 并进行预览。 quickLookController 对象有一个 currentPreviewItemIndex 属性可以用在这个地方。虽然逻辑上这里不太可能发生错误,但是我们最好还是确保将预览的文档在datasource数组里面而没有越界,否则app就会crash 掉。我要说的是这部分是文章最重要的地方 ,因为你在这里明确了 Quick Look Preview Controller 中需要打开预览的文档。
最后你需要将 Quick Look Preview Controller 展示出来。有两个方法可以实现这一目的:可以模态的展示,也可以将其压入一个navigation-based app的navigation 视图控制器里面。这两种我都会做介绍。
根据上面的讨论,现在我们就来开始写代码实现功能。如下所示的那样只有三行代码:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { if QLPreviewController.canPreviewItem(fileURLs[indexPath.row]) { quickLookController.currentPreviewItemIndex = indexPath.row navigationController?.pushViewController(quickLookController, animated: true) } }
正如你看见的我们首先检查了点击文档是否支持预览(在本文的demo中我们提早就知道的所以的文档都是支持预览的,但是检查工作还是必须的,尤其是在文档是从服务器抓取得到或者你并不清楚文件类型的时候)。接下来我们设置正确的文档索引,然后将视图控制器压入栈中。如果你想模态展示的话,可以将代码替换为:
presentViewController(quickLookController, animated: true, completion: nil)
如果你现在就运行demo并点击任意一行,你就能发现对应的文档在Quick Look Preview Controller中被打开了。
你可能已经注意到了,该视图控制器下面有带有两个案件的toolbar,左边的按键能够使你通过一个activity controller( UIActivityViewController )分享当前的文档。
该activity controller中的分享选项数量是可变的它取决于app在什么设备运行(真机的选项必然是多个模拟器的)以及当前设备上登录的社交帐号,等等。但是不管怎么你总是可以在任意时刻分享当前正在预览的文件。
在右侧按键将会使用视图展示出当前datasource所代表的文档。
用户可以将这个按键当作一个快捷按键这样用户就不用先退出Quick Look Preview Controller然后再选择一个文件进入Quick Look Preview Controller。可以直接在该视图里面对文件进行选择,而不需要返回原来的视图控制器中。
除了强制性的 QLPreviewControllerDataSource 协议和其中的两个方法之外,你还可以遵循 QLPreviewControllerDelegate 协议并实现其中的一些方法来对预览视图或者相关的动作做出更多的控制。需要注意的是第二个协议中的方法都试可选的也就是说如果你不实现其中的方法的话程序也不会存在什么问题。但是我们还是会做出一些实现做为本文的延伸拓展。
第一步当然是遵循 QLPreviewControllerDelegate 协议,所以你需要如下修改 *FileListViewController.swift 文件的类头部:
class FileListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, QLPreviewControllerDataSource, QLPreviewControllerDelegate
至于你要实现那些方法这取决于你自己的需求。你可以在官方的 文档 找到所有的协议委托方法。文中只会介绍其中的一部分。
在遵循该协议之后,我们就需要设置类做为quickLookController的委托对象了,因此我们在 viewDidLoad() 中添加下面这行代码:
override func viewDidLoad() { ... quickLookController.delegate = self }
我们首先来实现其中的两个委托方法,该方法会在预览视图即将关闭或者弹出的时候被Quick Look framework调用执行。具体来说其中的一个方法会在视图消失之前立即被调用另一个会在视图消失之后立刻被调用。为了更好的演示,这两个方法都会被实现并且每一个在调用之后都会在控制台打印一条消息来让我们知道函数以及被调用了。
func previewControllerWillDismiss(controller: QLPreviewController) { print("The Preview Controller will be dismissed.") }
在第二个委托代理方法中,除了打印控制台消息外我们还会设置tableview取消之前选择的那一行。这样在当前视图弹出的时候会触发一个动作。
func previewControllerDidDismiss(controller: QLPreviewController) { tblFileList.deselectRowAtIndexPath(tblFileList.indexPathForSelectedRow!, animated: true) print("The Preview Controller has been dismissed.") }
再次运行demo的话,你就会看到当预览视图被弹出的时候tableview中之前的选中行被自动取消选中了。
Quick Look Preview Controller中预览的文件中存在网页链接是非常正常的一件事。当用户点击链接的时候可能你允许用户打开链接也有可能你根本不会进行响应打开该链接。下面这个方法就是为了实现这一目的,在函数里面你可以按照自己的需求设置那些URL可以被打开。
func previewController(controller: QLPreviewController, shouldOpenURL url: NSURL, forPreviewItem item: QLPreviewItem) -> Bool { if item as! NSURL == fileURLs[0] { return true } else { print("Will not open URL /(url.absoluteString)") } return false }
上面的方法中除了一种特情况外其余文件中的URL都不允许打开。只有在URL是第一个PDF文档中的URL的话,我们允许打开并返回 true ,其余都返回 false 拒绝打开。再次运行app依次点击PDF和Word中的URL。看看会发生什么并注意控制台中的信息。你会发现前者的链接在浏览器中打开了,而后者没有。
到了文章的结尾部分,我相信你也可能会觉得Quick Look framework可能是iOS SDK中最简单的框架之一。实现它并对文件进行预览几乎没有任何难度,并且如果你有兴趣的话里面还有很多的可选委托方法供你探索。在我看来这个框架以及其中的预览视图控制器是非常有用的工具,尤其是当app需要处理文档的时候,因为该框架免费的为开发者提供了许多重要的功能。如果你即将使用该框架的话,本文可能会为你提供一些正确的指导。如果你还没有准备使用的话,你可以认真考虑一下该框架。
最后你可以下载完整的[代码]作为参考。