[Перевод] Создаем читалку eBook с помощью PDFKit на Swift

Добрый день! Представляю вашему вниманию продолжение статьи «Скачиваем, сохраняем и просматриваем PDF в Swift», как и обещал автор — немного подробнее рассмотрим PDFKit.

PDFKit появился в iOS 11, и в нем есть 3 замечательные функции, о которых бы я хотел рассказать, в процессе создания приложения читалки eBook: Разбиение по страницам, Отображение Содержания и Иконка страницы.

Всем, кто заинтересовался, добро пожаловать под кат.
image

image

image
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
}


image

Иконки страниц для удобной навигации

В 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)
}


image

Отображение содержания для навигации по главам

Чтобы попасть на определенную страницу, нам нужно передать в pdfView конкретную страницу (PDFPage). Значит нам надо углубиться в иерархию содержания (outline) в документе (document) чтобы попасть на нужную страницу (page).

image

Я использую UITableViewController как всплывающее окно (popOver). Поэтому мне надо:

  1. Создать протокол. UITableViewController должен следовать этому протоколу.
  2. Добавить некоторые методы делегата чтобы получить доступ к pdfView в PDFViewController.
  3. Вызвать метод делегата когда пользователь выберет определенную ячейку.
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)
        }
    }

}


image

Ура! Вот мы и применили 3 основные функции PDFKit. Есть еще некоторые тривиальные детали интерфейса, но в данной статье мы не будем на них фокусироваться. Проект доступен по ссылке всем желающим.

© Habrahabr.ru