iOS使用 Quick Look Framework 快速預覽文檔
在 iOS SDK 中可以發現很多不是很有名的框架或者庫。這些框架或庫大多數都可以為你節省很多時間,同時也證明它們自己的價值。其中,有一個叫 Quick Look Framework 的框架。即使你之前可能沒有聽說過它,但是你看到它的名字也就大概知道它的用途了;它可以為應用提供文檔預覽的功能。
Quick Look Framework 使用起來很簡單,它可以預覽特定類型的文件。它支持的文件類型有:
- iWork 文檔 (Pages,Numbers 和 Keynote)
- Microsoft Office 文檔 (只要是 Office 97 或更新的版本都支持)
- PDF 文件
- 圖片
- 文本文件
- 富文本格式文檔
- CSV
正如你現在所想的那樣,如果你的應用程序正在處理上述文件類型的文件,你希望用戶能夠預覽他們的內容,這個框架就會變得非常方便。不僅如此,Quick Look Framework 也提供了分享功能。發送或者共享預覽中的文檔會呈現一個 activity controller(UIActivityViewController
)。
開發者使用 Quick Look Framework 的主要任務是提供一個可以為 preview controller 打開特定文檔進行預覽的datasource
。該 datasource 實際上是一個 NSURL
對象的列表,它指定了每個文檔的路徑,該路徑可以是本地存儲的,也可以是網絡地址。本地存儲的文件包括存在 documents directory 或者 bundle 等。
Quick Look Framework 提供了一個叫 QLPreviewController
(Quick Look Preview Controller) 的視圖控制器,用來快速查看一個文檔。該視圖控制器可以以模態的形式呈現,如果應用有 navagation 也可以通過壓入 navigation 棧來呈現。它是 Quick Look Framework 重要的一個組件,一旦它呈現出來,就會提供分享選項,也可以在不關閉預覽控制器的情況下切換所有其它可以預覽的文檔。另外,Quick Look Framework 有兩個 datasource 方法需要實現,這兩個方法屬于QLPreviewControllerDataSource
協議。除此之外,還有個 QLPreviewControllerDelegate
協議是可選實現的,如果實現的話,能夠豐富 Quick Look Framework 的其它功能。
我們會在接下來的部分討論具體細節,也會像之前那樣用一個具體的 demo 來演示。然而在開始前,我們先快速瀏覽一下用來演示的 demo。
關于 demo APP
為了教程的展示需要,我們會使用 navigation 和一個視圖控制器。該視圖控制器(FileListViewController)會在 tableview 中展示一些文件,這些文件保存在應用的 bundle 里。我們不會使用所有支持的文件類型;只是選用了其中的一部分來說明 Quick Look Framework 是如何使用的。
當點擊 tableview 中對應于文件的那一行時,Quick Look Preview Controller 會打開對應的文檔進行預覽。Quick Look Preview Controller 會被壓入 navigation 棧中,當然我也會展示如何以模態的形式呈現(實際上,與其它呈現模態的視圖控制器方式是完全一樣的)。最后,我們也會講一下額外提供的功能(比如,分享和切換文檔等功能),以及討論一下另外可選的代理方法。
接下來的截圖展示的是初始的視圖控制器(FileListViewController
)。正如你看到的,每一行都會顯示文件名以及其對應的文件類型。
當一個文檔被選中,預覽如下:
你可以從這里下載工程的初始代碼。下載之后,你可以打開快速預覽一下,接下去我們會一步步地引導你走下去。
文件以及文件的 URL
在初始工程中,你會發現一組我們在 demo 中展示的示例文件。這些文件已經添加到了應用的 bundle 中了,但是要顯示預覽還遠遠不夠。我們的職責就是來告訴應用,到底哪個文件是需要通過 Quick Look framework 預覽的。
目標已經很明確了,我們創建一個數組,用來保存示例文件的文件名。這些文件名主要有兩個用處:
- 在 tableview 中正確顯示文件名和相關說明
- 最重要的是,可以根據這個數組來創建
NSURL
對象的列表,該列表可以作為 Quick Look framework 需要的 datasource,這樣它就能夠檢索和預覽每一個文件了。
在熟悉每個文件名的用處之后,是時候讓我們來碼代碼了。第一件事情就是創建一個保存文件名的數組(包含文件的擴展名)。打開 Xcode 中的 FileListViewController.swift
文件,添加以下代碼到文件開頭:
let fileNames = ["AppCoda-PDF.pdf", "AppCoda-Pages.pages", "AppCoda-Word.docx", "AppCoda-Keynote.key", "AppCoda-Text.txt", "AppCoda-Image.jpeg"] |
在我們的例子中,我們已經知道了需要預覽文件的文件名,因此聲明和使用 fileNames
數組都很容易。但是,真實應用中不可能事先就讓你保存有所有需要預覽的文件的(文件可能需要下載等)。在這種情況下,你必須動態地添加數據到這個數組中,再從數組中獲取相應的文件名。
現在,讓我們先聲明:
var fileURLs = [NSURL]() |
該數組不僅會作為 Quick Look framework 的 datasource,也會作為 tableview 的 datasource。
現在讓我們創建一個新的方法,將值添加到以上的數組中去。在該新方法中,我們會逐個獲取 fileNames
數組中的文件名,并相應地創建一個 NSURL 對象。一旦遍歷完所有的文件,我們就將所有新建的 NSURL 對象添加到 fileURLs
數組中,這部分的工作就算完成了。
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() } |
展示文件
我們已經將 datasource 準備好了,接著會展示需要預覽的文件,以及關于它們類型的說明。值得注意的是,這一部分的內容與我們要使用的 Quick Look framework 并沒有什么關系,之所以放在文章里是為了讓你對自己的工程有點想法。
我們需要實現的效果如下:
我們將從每個 NSURL
對象入手,最終獲取到對應文件的文件名和擴展名。為了實現這個功能,我們需要將 NSURL
分割。分割的最后一個部分應該是完整的文件名,繼續分割就可以得到文件名和擴展名了。
我上面講的過程都會在一個新方法中實現。即便下一段代碼中并沒有什么難度,我還是為每行代碼添加了注釋。值得注意的是,該方法返回的不是簡單的一個值,而是一個 tuple
。tuple 中第一個值為文件名,第二個值為文件的擴展名。
func extractAndBreakFilenameInComponents(fileURL: NSURL) -> (fileName: String, fileExtension: String) { // 將 NSURL 路徑分割成零組件,然后創建一個數組將其放置其中 let fileURLParts = fileURL.path!.componentsSeparatedByString("/") // 從上面數組的最后一個元素中得到文件名 let fileName = fileURLParts.last // 將文件名基于符號 . 分割成不同的零組件,并放置在數組中返回 let filenameParts = fileName?.componentsSeparatedByString(".") // 返回最終的元組 return (filenameParts![0], filenameParts![1]) } |
上面這個方法是一個有用的工具,它主要有兩個功能:
- 我們會將 tuple 的第一個值顯示在 tableview 中。
- 我們會針對文件擴展名展示簡短的說明。
讓我們一個個來,先來看看 tableview 中的 tableView(tableView:cellForRowAtIndexPath)
方法。將它改成下面的代碼(在 cell 重用和返回之間添加相應的代碼),這樣你就可以在 cell 中顯示文件名了。
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)
方法,讓我們來添加一行代碼來調用上面的方法吧,這樣我們就可以在每個 cell 里展示文件類型的描述了。
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 } |
如果你已經跟進到這里了,那就嘗試著運行一下這個應用吧。沒什么大問題的話,你的應用也會和文章開頭的截圖一樣。
Quick Look Preview Controller Datasource
使用 Quick Look framework 的第一件事情就是在類中導入頭文件。因此,我們需要在 FileListViewController.swift
文件中,最上面的位置添加如下代碼:
import QuickLook |
現在我們要聲明并初始化一個使用 Quick Look framework 重要的對象。在 FileListViewController
類的最上面添加如下代碼:
let quickLookController = QLPreviewController() |
正如你所見,我更傾向于聲明一個全局的 QLPreviewController
對象,當然這不是強制的,你也可以不這么做。另一種方法就是使用局部對象來觸發并顯示 Quick Look Preview Controller。
在我們使用 quickLookController
對象之前,我們需要接受 QLPreviewControllerDataSource
協議。這是必須要做的,因為之前提到的一大串方法都需要有 datasource 才行。然而,我們要先在類名之后追加這個協議:
class FileListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, QLPreviewControllerDataSource |
現在讓我們將注意力集中在必須要實現的兩個方法上吧。正如我之前提到的,這兩個方法會將對應文檔的預覽在特定情況下展現出來。顯然,我們很早前創建的 fileURLs
數組就要派上用場了。
第一個方法指定了有多少個文件需要在 Quick Look Preview Controller 中預覽。在我們的 demo 中這個數字就等于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 對象(簡單起見,將 NSURL 和 QLPreviewItem 視為是相同的)。你可能覺得QLPreviewItem
協議中支持的方法很有用,那你也可以在這里看看如何自定義 QLPreviewItem
對象。
以上兩個方法在你每次使用 QLPreviewControllerDataSource
時候都是必須的。請一定要確保 Quick Look Preview Controller 需要預覽的文件總數與作為 datasource 的數組元素總數相同,否則你回遇到訪問數組越界的問題的。
我們還有最后一步不能忘記:那就是必須將該類設置為 quickLookController
對象的 datasource。在 viewDidLoad()
方法中,添加如下代碼:
override func viewDidLoad() { ... quickLookController.dataSource = self } |
預覽文檔
我們已經設置好了 Quick Look Preview Controller 的 datasource 并實現了兩個相關的方法,是時候讓我們的 demo 應用響應點擊并預覽選中的文檔了。因此,我們需要實現如下的 tableview 的方法:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { } |
你需要一直記著一個基本且簡單的規則,就是確保你想預覽的文檔能被 Quick Look framework 打開。QLPreviewController
類提供了一個叫 canPreviewItem(:)
的類方法來解決這個問題。該方法返回一個 Bool
值,且當返回 true
時,該文檔的類型是可以被預覽的,否則文檔是不能打開的。毫無疑問,你應該在該方法返回 true
時,才繼續執行接下來的預覽操作。
如果你按照我之前說的將數組下標做了對應的話,quickLookController
對象有一個叫 currentPreviewItemIndex
的屬性可以實現這個用途。雖然在邏輯上很難在這里出錯,但是為了確保下標不越界(例子中的是 fileURLs
數組)也值得了,否則你可能會看到你的應用奔潰。我可以說這是最重要的部分,告訴 Quick Look Preview Controller 應該打開哪個文檔。
最后,肯定要將 Quick Look Preview Controller 顯示出來的。有兩個方案可以實現:一種是以模態的形式,另一種是以 navigation 壓棧的形式(前提是要有 navigation controller)。兩種方式都會在下面展示。
原理已經在上面講清楚了,接下來讓我們來碼代碼吧。下面有三行代碼:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { if QLPreviewController.canPreviewItem(fileURLs[indexPath.row]) { quickLookController.currentPreviewItemIndex = indexPath.row navigationController?.pushViewController(quickLookController, animated: true) } } |
正如你看到,我們首先要確保點擊的文檔是可以被打開的(在這個 demo 應用中我們事先是知道所有的文檔都能被打開,但是這個判斷的操作還是必要的,尤其從服務器獲取一個文件而不確定它的類型時)。接著,我們指定了需要打開的文檔在數組中對應的下標,最后我們將 quickLookController
壓入 navigation 的棧里。換種方式,我們也可以通過下面的代碼來以模態的形式顯示:
presentViewController(quickLookController, animated: true, completion: nil) |
如果你現在運行 demo 的話,你點擊某行就會看到對應文檔的預覽效果了。
Quick Look Preview Controller 提供的其他特性
可能你已經注意到了,Quick Look Preview Controller 底部有個 toolbar,其中有兩個按鈕。左邊的按鈕允許你通過UIActivityViewController
發送并分享當前預覽的文檔。
活動控制器中可用的共享選項的數量和種類是不定的,它取決于設備上運行的應用程序(一般模擬器上提供的選項比真機要少)和登錄的社交網絡,但是不管是什么,你都可以使用左側導航按鈕無延遲地立即發送或共享要被預覽的文檔。
右側第二個 bar button item 呈現了一個模態 view controller,用來展示 datasource 中所有可用的 documents 列表。
該按鈕可看做是一個快捷鍵,這樣切換不同的文檔時就可以不用退出預覽界面了。此功能可以方便用戶直接的查看所有文檔的預覽,而沒必要退回到原來的視圖控制器。
Quick Look Preview Controller Delegate
除了強制地接受了 QLPreviewControllerDataSource
協議并實現了其中的兩個必須實現的方法,你也可以遵守QLPreviewControllerDelegate
協議并實現該協議來更好地控制 Quick Look Preview Controller。值得注意的是,這里協議里的方法都是可選實現的,也就是說不實現,應用也能正常運行。然而,我們可以在本教程中做一些擴展。
第一步當然是接受 QLPreviewControllerDelegate
協議,所以你需要修改 FileListViewController.swift
文件中的相應部分如下:
class FileListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, QLPreviewControllerDataSource, QLPreviewControllerDelegate |
具體需要的方法可以根據你的需求來添加。該代理中提供的方法可以在官方文檔里查到。我們會介紹其中的一部分。
在我們接受這個協議后,很重要的一步就是將類和代理鏈接起來,因此我們在 viewDidLoad()
方法中添加如下代碼:
override func viewDidLoad() { ... quickLookController.delegate = self } |
我們會用兩個其中的方法來展示,這兩個方法是在 preview controller 將要退出時被 Quick Look framework 調用的。實際上,一個是在退出前調用的,另一個在退出后調用的。我們會在 demo 中實現這兩個方法,并在控制臺中簡單地打印一些語句表示應用執行到了這里。先從第一個開始吧:
func previewControllerWillDismiss(controller: QLPreviewController) { print("The Preview Controller will be dismissed.") } |
第二個代理方法中會將之前選中的單元行進行取消選中,另外還會在控制臺打印一條消息。該方法會在 preview controller 退出后觸發。
func previewControllerDidDismiss(controller: QLPreviewController) { tblFileList.deselectRowAtIndexPath(tblFileList.indexPathForSelectedRow!, animated: true) print("The Preview Controller has been dismissed.") } |
現在運行這個應用,你可以驗證剛才寫的那些功能是否有實現。
對于 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 外,其他都不允許打開(return false
)。如果鏈接指向的 URL 是 PDF 文檔(第一種 URL)的話,就允許打開并返回 true。再次運行應用,首先點擊 PDF 文檔,再點擊 Word 文檔。觀察一下有什么區別。你會發現 Word 文檔的鏈接是不能打開的,而 PDF 文檔是可以在 Safari 中打開的。
總結
終于到文章的最后,我想我們應該都能贊同 Quick Look framework 是 iOS SDK 中最簡單的框架之一了。通過它可以很簡單地實現文檔預覽的功能,如果有其他特殊的需求還可以利用可選的代理方法來實現。依我看來,Quick Look framework 和 Quick Look Preview Controller 針對需要處理文檔的應用來說是一個很好的工具。如果你打算將 Quick Look framework 運用到你的應用中的話,那本文希望可以讓你下定決心使用它。如果你沒這個打算的話,那本文也會讓你考慮一下。
你可以在 Github 上下載完整的工程作為參考。
來源:http://swift.gg/2016/04/29/quick-look-framework/