[Перевод] Создаем читалку eBook с помощью PDFKit на Swift
Добрый день! Представляю вашему вниманию продолжение статьи «Скачиваем, сохраняем и просматриваем PDF в Swift», как и обещал автор — немного подробнее рассмотрим PDFKit.
PDFKit появился в iOS 11, и в нем есть 3 замечательные функции, о которых бы я хотел рассказать, в процессе создания приложения читалки eBook: Разбиение по страницам, Отображение Содержания и Иконка страницы.
Всем, кто заинтересовался, добро пожаловать под кат.
Paging, Outline, и Thumbnails
Создание PDFViewController при помощи Initializer Injection
При создании переменной URL в PDFViewController, мы сделали её неизменяемой. Именно поэтому я выбрал Initializer Injection, а не Property Injection или Method Injection.
Единственная внедряемая переменная, это pdfUrl, а остальные: document и outline, доступны через область локального класса и могут быть инициированы через init ().
Содержимое pdfView у нас не меняется, поэтому я обозначил его переменной document и так же инициализировал через init ().
import UIKit
import PDFKit
class PDFViewController: UIViewController {
private let pdfUrl: URL
private let document: PDFDocument!
private let outline: PDFOutline?
private var pdfView = PDFView()
init(pdfUrl: URL) {
self.pdfUrl = pdfUrl
self.document = PDFDocument(url: pdfUrl)
self.outline = document.outlineRoot
pdfView.document = document
super.init(nibName: nil, bundle: nil)
}
...
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(pdfView)
}
}
import UIKit
class ViewController: UIViewController {
@IBAction func openPdfPressed(_ sender: Any) {
guard let path = Bundle.main.url(forResource: "swift", withExtension: "pdf") else {
print("failed to unwrap fileURL")
return
}
let pdfViewController = PDFViewController(pdfUrl: path)
present(pdfViewController, animated: true, completion: nil)
}
}
Устанавливаем горизонтальное разбиение по страницам
Теперь у нас есть содержимое в документе, которое мы прописали в pdfView.document. Для того, чтобы сделать горизонтальный скролл, как в книге, нужно добавить несколько настроек в PDFView.
private func setupPDFView() {
view.addSubview(pdfView)
pdfView.displayDirection = .horizontal
pdfView.usePageViewController(true)
pdfView.pageBreakMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
pdfView.autoScales = true
}
Иконки страниц для удобной навигации
В PDFKit есть потрясающе удобная вещь, такая как ThumbnailView. Все, что нужно сделать — это назначить наш pdfView свойству thumbnailView.pdfView. И все! Никаких делегатов, уведомлений и других настроек.
private func setupThumbnailView() {
thumbnailView.pdfView = pdfView
thumbnailView.backgroundColor = UIColor(displayP3Red: 179/255, green: 179/255, blue: 179/255, alpha: 0.5)
thumbnailView.layoutMode = .horizontal
thumbnailView.thumbnailSize = CGSize(width: 80, height: 100)
thumbnailView.contentInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
view.addSubview(thumbnailView)
}
Отображение содержания для навигации по главам
Чтобы попасть на определенную страницу, нам нужно передать в pdfView конкретную страницу (PDFPage). Значит нам надо углубиться в иерархию содержания (outline) в документе (document) чтобы попасть на нужную страницу (page).
Я использую UITableViewController как всплывающее окно (popOver). Поэтому мне надо:
- Создать протокол. UITableViewController должен следовать этому протоколу.
- Добавить некоторые методы делегата чтобы получить доступ к pdfView в PDFViewController.
- Вызвать метод делегата когда пользователь выберет определенную ячейку.
import UIKit
import PDFKit
protocol OutlineDelegate: class {
func goTo(page: PDFPage)
}
class OutlineTableViewController: UITableViewController {
let outline: PDFOutline
weak var delegate: OutlineDelegate?
init(outline: PDFOutline, delegate: OutlineDelegate?) {
self.outline = outline
self.delegate = delegate
super.init(nibName: nil, bundle: nil)
}
...
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return outline.numberOfChildren
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell =
tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as UITableViewCell
if let label = cell.textLabel, let title = outline.child(at: indexPath.row)?.label {
label.text = String(title)
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let page = outline.child(at: indexPath.row)?.destination?.page {
delegate?.goTo(page: page)
}
}
}
Ура! Вот мы и применили 3 основные функции PDFKit. Есть еще некоторые тривиальные детали интерфейса, но в данной статье мы не будем на них фокусироваться. Проект доступен по ссылке всем желающим.