前言
iOS 11 后苹果在iOS平台开放了PDFKit SDK,该框架主要是用来显示和操作PDF文件的,本次通过学习PDFKit实现了展示PDF内容、缩略图、目录、收藏和搜索等功能。
代码 iBook,纯Swift编写,想学习PDFKit,可以点一下Star
学习学习。
PDFKit
PDFKit中的文档说明
1
2
3
// Views
PDFView: PDFView用来展示pdf文件
PDFThumbnailView: 通过PDFThumbnailView可以获取到pdf的缩略图
1
2
3
4
5
// Model
PDFDocument: 表示PDF数据或PDF文件,并定义写入,搜索和选择PDF数据的方法。
PDFPage: 表示PDF中的页,可以通过PDFPage获取每一页中的相关信息。
PDFOutline: 表示PDF文档结构的树结构层次结构中的元素,可以获取PDF的大纲目录信息。
PDFSelection: 标识PDF文档中文本的连续或非连续选择的文字,表示选择一段文字,在搜索模块可以搜索文字。
1
2
3
4
5
// Annotations
PDFAnnotation: 表示PDF文档中的注释。
PDFAction: 激活PDF注释或单击大纲目录时执行的跳转操作
PDFDestination: 描述PDF页面上的跳转目录,在跳转页中使用。
PDFBorder: 可选的注释边框,完全在注释矩形内绘制。
iBook
书库页面获取PDF相关数据, 可以通过KVC获取。
- PDF书名
1 2 3
if let title = documentAttributes["Title"] as? String { cell.title = title }
- PDF作者
1
2
3
if let author = documentAttributes["Author"] as? String {
cell.author = author
}
- 获取第一页作为封面,封面已经做了缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let thumbnailCache = NSCache<NSURL, UIImage>()
private let downloadQueue = DispatchQueue(label: "com.jovins.pdfview.thumbnail")
if let page = document.page(at: 0), let key = document.documentURL as NSURL? {
cell.url = key
if let thumbnail = thumbnailCache.object(forKey: key) {
cell.image = thumbnail
} else {
downloadQueue.async {
let imgWidth: CGFloat = (UIScreen.main.bounds.width - 48)/2 - 24
let imgHeight: CGFloat = 190
let thumbnail = page.thumbnail(of: CGSize(width: imgWidth, height: imgHeight), for: .cropBox)
self.thumbnailCache.setObject(thumbnail, forKey: key)
if cell.url == key {
DispatchQueue.main.async {
cell.image = thumbnail
}
}
}
}
}
初始化一个DocumentModel模型存储PDFDocument数据。
1
2
3
4
5
6
7
struct DocumentModel {
var title: String = ""
var author: String = ""
var coverImage: UIImage?
var url: URL?
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class BookManager {
static let shared = BookManager()
func getDocument(_ pdfDoc: PDFDocument?) -> DocumentModel {
var model = DocumentModel()
if let document = pdfDoc, let documentAttributes = document.documentAttributes {
if let title = documentAttributes["Title"] as? String {
model.title = title
} else {
model.title = "No Title"
}
if let author = documentAttributes["Author"] as? String {
model.author = author
} else {
model.author = "No Author"
}
if document.pageCount > 0, let page = document.page(at: 0) {
let imgWidth: CGFloat = (UIScreen.main.bounds.width - 48)/2 - 24
let imgHeight: CGFloat = 190
let thumbnail = page.thumbnail(of: CGSize(width: imgWidth, height: imgHeight), for: .cropBox)
model.coverImage = thumbnail
}
if let url = document.documentURL {
model.url = url
}
}
return model
}
}
PDF浏览
图一是PDF浏览详情页
1
2
3
4
5
6
7
8
9
10
private lazy var pdfView: PDFView = {
let view = PDFView()
view.backgroundColor = Device.bgColor
view.autoScales = true
view.displayMode = .singlePage
view.displayDirection = .horizontal
view.usePageViewController(true, withViewOptions: [UIPageViewController.OptionsKey.spineLocation: 20])
return view
}()
self.pdfView.document = self.document
图二是该PDF的所有页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// 该页面的核心代码
if let doc = self.document, let page = doc.page(at: indexPath.item) {
let pageNumber = indexPath.item
cell.pageNumber = pageNumber
let key = NSNumber(value: pageNumber)
if let thumbnail = self.thumbnailCache.object(forKey: key) {
cell.image = thumbnail
} else {
let cellSize = CGSize(width: (UIScreen.main.bounds.width - 16 * 4)/3, height: 140)
downloadQueue.async {
let thumbnail = page.thumbnail(of: cellSize, for: .cropBox)
self.thumbnailCache.setObject(thumbnail, forKey: key)
if cell.pageNumber == pageNumber {
DispatchQueue.main.async {
cell.image = thumbnail
}
}
}
}
}
图三是该PDF的目录
1
2
3
4
5
6
7
8
9
10
11
12
/// 该页面核心代码
let outline = self.lines[indexPath.item]
cell.titleString = outline.label
cell.pageString = outline.destination?.page?.label
var indentationLevel = -1
var parent = outline.parent
while let _ = parent {
indentationLevel += 1
parent = parent?.parent
}
cell.indentationLevel = indentationLevel
图四是浏览时收藏的页面。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/// 获取收藏页
if let documentURL = self.document?.documentURL?.absoluteString, let bookmarks = UserDefaults.standard.array(forKey: documentURL) as? [Int] {
self.bookmarks = bookmarks
self.collection.reloadData()
}
// 显示收藏
let pageNumber = self.bookmarks[indexPath.item]
if let page = self.document?.page(at: pageNumber) {
cell.pageNumber = pageNumber
let key = NSNumber(value: pageNumber)
if let thumbnail = self.thumbnailCache.object(forKey: key) {
cell.image = thumbnail
} else {
let cellSize = CGSize(width: (UIScreen.main.bounds.width - 16 * 4)/3, height: 140)
downloadQueue.async {
let thumbnail = page.thumbnail(of: cellSize, for: .cropBox)
self.thumbnailCache.setObject(thumbnail, forKey: key)
if cell.pageNumber == pageNumber {
DispatchQueue.main.async {
cell.image = thumbnail
}
}
}
}
}
打印,直接调取系统方法。
1
2
3
let printInteractionController = UIPrintInteractionController.shared
printInteractionController.printingItem = self.document?.dataRepresentation()
printInteractionController.present(animated: true, completionHandler: nil)
阅读历史
在浏览页存储历史记录
private func storgeHistoryList() {
if let documentURL = self.document?.documentURL?.absoluteString {
let cache = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].absoluteString
let key = cache.appending("com.jovins.ibook.storgeHistory")
if var documentURLs = UserDefaults.standard.array(forKey: key) as? [String] {
if !documentURLs.contains(documentURL) {
// 不存在则存储
documentURLs.append(documentURL)
UserDefaults.standard.set(documentURLs, forKey: key)
}
} else {
// 第一次存储
UserDefaults.standard.set([documentURL], forKey: key)
}
}
}
获取已浏览的记录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fileprivate func refreshData() {
let cache = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].absoluteString
let key = cache.appending("com.jovins.ibook.storgeHistory")
if let documentURLs = UserDefaults.standard.array(forKey: key) as? [String] {
var urls: [URL] = []
for str in documentURLs {
if let url = URL(string: str) {
urls.append(url)
}
}
self.documents = urls.compactMap { PDFDocument(url: $0) }
self.tableView.reloadData()
}
}
搜索
通过PDFDocument设置代理,然后调用beginFindString
可以实现搜索功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let searchText = searchBar.text!.trimmingCharacters(in: CharacterSet.whitespaces)
if searchText.count < 3 {
return
}
self.searchResults.removeAll()
self.tableView.reloadData()
if let document = self.document {
document.cancelFindString()
document.delegate = self
document.beginFindString(searchText, withOptions: .caseInsensitive)
}
}
/// 代理实现的方法,可以获取到搜索结果
func didMatchString(_ instance: PDFSelection) {
self.searchResults.append(instance)
self.tableView.reloadData()
}
未来
该项目未来会增加txt
、epub
浏览功能。