ちょっと話題の記事

[iOS 11] PDFKit入門

2017.09.20

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

iOS11から新しくPDFKitが追加されました。以前からMac用のSDKにはPDFKitが含まれていましたが、iOSにはありませんでした。PDFKitの登場前はWebViewなどで表示しており、細かい制御はできませんでした。それが今回のPDFKit追加によって、表示だけではなくて色々な制御ができるようになります。

この記事では、PDFKitの概要と基本的な機能のサンプルをご紹介します。

PDFKitの概要

PDFKitはいくつかのユーティリティクラスに分かれています。

PDF概要

PDFView

ビューアー的な機能を持ちます。

PDFDocument

PDFデータまたはPDFファイルを指します。PDFデータの書き込み、検索、選択などの機能があります。

PDFPage

ページ単位の制御部分です。テキストや注釈、描画にまつわる機能があります。

PDFAnnotation

ページの中の注釈部分です。リンクやフォームなどの機能があります。

PDFOutline

PDFドキュメントの構造です。ツリー構造で表されます。

PDFSelection

PDFドキュメント内のテキスト選択を識別します。

PDFDestination

PDFページ上のポイントを記述します。

PDFThumbnailView

PDFのサムネイル機能を持ちます。

サンプルコード

この記事内のサンプルは以下の下記環境で試しています。

Xcode 9.0
Swift 4.0

PDFを表示する (PDFView)

まずは基本の表示からです。Resources内にあるsample01.pdfを表示させる手順です。

1. Storyboard上にUIViewを配置

PDFを表示させたいUIViewControllerにUIViewを配置します。

1.PDFView

配置したら適宜制約を付けます。

2.PDFView_適宜制約を定義

2. PDFViewクラスの設定

配置したUIViewのCustom ClassにPDFViewを設定します。

3.PDFView_Viewを選択してCustomClassに入れる

3. Outlet接続

PDFViewをOutletで接続します。接続する前に、

import PDFKit

を追加します。

4.PDFView_Outlet接続

4. コード記載

PDFをPDFViewに表示させます。PDFViewの.documentプロパティにPDFDocumentを設定すればOKです。

import UIKit
import PDFKit

class ViewController: UIViewController {

    @IBOutlet weak var pdfView: PDFView!

    override func viewDidLoad() {
        super.viewDidLoad()

        if let documentURL = Bundle.main.url(forResource: "sample01", withExtension: "pdf") {
            if let document = PDFDocument(url: documentURL) {
                pdfView.document = document
            }
        }
     }
実行結果

PDF表示結果

背景色を変える (PDFView)

ビューアーの背景色はPdfViewの.backgroundColorプロパティで設定できます。

        if let documentURL = Bundle.main.url(forResource: "sample01", withExtension: "pdf") {
            if let document = PDFDocument(url: documentURL) {
                pdfView.backgroundColor = UIColor.lightGray
                pdfView.document = document
            }
        }
実行結果

それぞれ、

  • pdfView.backgroundColor = UIColor.lightGray
  • pdfView.backgroundColor = UIColor.orange

とした時の表示です。

Viewerの背景色変更

オートスケール (PDFView)

オートスケールをtrueにすると、初期表示が画面サイズにピッタリ入るように表示されます。PdfViewの.autoScalesプロパティにBool値を入れます。

        if let documentURL = Bundle.main.url(forResource: "sample01", withExtension: "pdf") {
            if let document = PDFDocument(url: documentURL) {
                pdfView.autoScales = true
                pdfView.backgroundColor = UIColor.lightGray
                pdfView.document = document
            }
        }
実行結果

それぞれ、

  • pdfView.autoScales = true
  • pdfView.autoScales = false

とした時の表示です。

オートスケール

余白 (PDFView)

余白を消す場合

PDFViewで表示する際に余白を消すには、.displaysPageBreakプロパティをfalseにします。設定しない(デフォルト値)はtrueになります。

        if let documentURL = Bundle.main.url(forResource: "sample01", withExtension: "pdf") {
            if let document = PDFDocument(url: documentURL) {
                pdfView.autoScales = true
                pdfView.backgroundColor = UIColor.lightGray
                pdfView.displaysPageBreaks = false
                pdfView.document = document
            }
        }
任意の余白をいれる

PDFViewに任意の余白を入れるには、.pageBreakMarginsプロパティにUIEdgeInsetsを設定します。この時、.displaysPageBreaksプロパティをfalseにすると.pageBreakMarginsは常に{0.0、0.0、0.0、0.0}を返すようになるので注意が必要です。

        if let documentURL = Bundle.main.url(forResource: "sample01", withExtension: "pdf") {
            if let document = PDFDocument(url: documentURL) {
                pdfView.autoScales = true
                pdfView.backgroundColor = UIColor.lightGray
                pdfView.displaysPageBreaks = true
                pdfView.pageBreakMargins = UIEdgeInsets(top: 10.0, left: 40.0, bottom: 10.0, right: 0.0)
                pdfView.document = document
            }
        }
実行結果

それぞれ、

  • pdfView.displaysPageBreaks = false
  • pdfView.displaysPageBreaks = true
  • pdfView.displaysPageBreaks = true pdfView.pageBreakMargins = UIEdgeInsets(top: 10.0, left: 40.0, bottom: 10.0, right: 0.0)

とした時の表示です。

余白

表示モード (PDFView)

PDFの仕様として5種類のボックス値を持っています。表示用にそれらのどれを使うか設定することができます。デフォルトではCropBoxです。

  • Crop Box
  • Trim Box
  • Art Box
  • Media Box
  • Bleed Box
// Crop Box
pdfView.displayBox = .cropBox
// Trim Box
pdfView.displayBox = .trimBox
// Art Box
pdfView.displayBox = .artBox
// Media Box
pdfView.displayBox = .mediaBox
// Bleed Box
pdfView.displayBox = .bleedBox
参考

スクロールの方向を変更する (PDFView)

ビューアーのスクロール方向を変更するには.displayDirectionプロパティを変更します。縦の場合は.vertical、横の場合は.horizontalを設定します。デフォルト値は.verticalです。

        if let documentURL = Bundle.main.url(forResource: "sample01", withExtension: "pdf") {
            if let document = PDFDocument(url: documentURL) {
                pdfView.autoScales = true
                pdfView.backgroundColor = UIColor.lightGray
                pdfView.displayDirection = .horizontal // 横スクロール
                pdfView.document = document
            }
        }
実行結果

それぞれ、

  • pdfView.displayDirection = .vertical
  • pdfView.displayDirection = .horizontal

とした時の表示です。

向きの違い

表示モードを変更する (PDFView)

PDFViewの表示モードには以下の4パターンがあります。

表示モード概要

  • 単一ページ/全ページをスクロールで表示 .singlePageContinuous
  • 単一ページのみ .singlePage
  • 見開きページ/前ページをスクロールで表示 .twoUpContinuous
  • 見開きページのみ .twoUp

デフォルトでは.singlePageContinuousとなります。表示モードを変更するには、.displayModeプロパティを設定します。

// 単一/全ページをスクロール表示(デフォルト)
pdfView.displayMode = .singlePageContinuous
// 単一ページのみ
pdfView.displayMode = .singlePage
// 見開き/全ページをスクロール表示
pdfView.displayMode = .twoUpContinuous
// 見開きページのみ
pdfView.displayMode = .twoUp

見開きの順番を変える (.twoUpContinuous/.twoUp) 

.twoUpContinuousまたは.twoUpの見開きにした時に、ベージの順番を右から左にするには.displaysRTLプロパティをtrueにします。

見開きの向き

// ページの表示を左から右のままにする(デフォルト)
pdfView.displaysRTL = false
// ページの表示を右から左にする
pdfView.displaysRTL = true

見開きの時に最初のページを表紙として扱う (.twoUpContinuous/.twoUp) 

.twoUpContinuousまたは.twoUpの見開きにした時に、最初のページを本の表紙として扱い、単独表示をするには.displaysAsBookプロパティをtrueにします。

.displaysAsBook

// 最初のページから見開きを適用する(デフォルト)
pdfView.displaysAsBook = false
// 最初のページを表紙として扱い、単独で表示させる
pdfView.displaysAsBook = true

PageViewControllerを利用する

usePageViewController(true)メソッドを呼び出すとビューアーの方でpageViewControllerを利用して表示するようになります。その際、displayModeは無視されます。

        if let documentURL = Bundle.main.url(forResource: "sample01", withExtension: "pdf") {
            if let document = PDFDocument(url: documentURL) {
                pdfView.usePageViewController(true)
                pdfView.document = document
            }
        }

ページ遷移 (PDFView)

PDFViewにはページを遷移するメソッドが用意されています。下記はボタンを押した時にページ遷移させるサンプルです。

次のページ

PDFViewの.canGoToNextPage()で次のページへ遷移できるかどうかのBool値が返ってきます。 .goToNextPage()で次のページに遷移することができます。

    @IBAction func didTapNextButton(_ sender: Any) {
        if pdfView.canGoToNextPage() {
            pdfView.goToNextPage(nil)
        }
    }

前のページ

PDFViewの.canGoToPreviousPage()で前のページへ遷移できるかどうかのBool値が返ってきます。 .goToPreviousPage()で前のページに遷移することができます。

    @IBAction func didTapPreviousButton(_ sender: Any) {
        if pdfView.canGoToPreviousPage() {
            pdfView.goToPreviousPage(nil)
        }
    }

最初のページ

PDFViewの.canGoToFirstPage()で最初のページへ遷移できるかどうかのBool値が返ってきます。 .goToFirstPage()で最初のページに遷移することができます。

    @IBAction func didTapFirstButton(_ sender: Any) {
        if pdfView.canGoToFirstPage() {
            pdfView.goToFirstPage(nil)
        }
    }

最後のページ

PDFViewの.canGoToLastPage()で最後のページへ遷移できるかどうかのBool値が返ってきます。 .goToLastPage()で最後のページに遷移することができます。

    @IBAction func didTapLastButton(_ sender: Any) {
        if pdfView.canGoToLastPage() {
            pdfView.goToLastPage(nil)
        }
    }

サムネイルを表示させる (PDFThumbnail)

PDFViewと同じようにStoryboard上にViewを配置して、Custom ClassにPDFThumbnailを設定します。

PDFThumbnailの配置

そして、PDFThumbnailをOutletで接続します。

Outlet接続

そして、PDFThumbnailViewの.pdfViewにPDFViewを設定します。.layoutModeでサムネイルのレイアウト(方向)を設定、.thumbnailSizeでサムネイルのサイズを指定、.backgroundColorでサムネイルの背景色を設定することができます。

import UIKit
import PDFKit

class ViewController: UIViewController {

    @IBOutlet weak var pdfView: PDFView!
    @IBOutlet weak var pdfThumbnailView: PDFThumbnailView!

    override func viewDidLoad() {
        super.viewDidLoad()

        if let documentURL = Bundle.main.url(forResource: "sample01", withExtension: "pdf") {
            if let document = PDFDocument(url: documentURL) {
                pdfView.autoScales = true
                pdfView.backgroundColor = UIColor.lightGray
                pdfView.document = document
            }            
        }
        pdfThumbnailView.pdfView = pdfView
        pdfThumbnailView.layoutMode = .horizontal
        pdfThumbnailView.backgroundColor = UIColor.gray
        pdfThumbnailView.thumbnailSize = CGSize(width: 40, height: 40)
    }
実行結果

画面の下に横向きのサムネイルを入れました。

サムネイルを表示

パスワードが設定されているPDFの扱い (PDFDocument)

パスワードが設定されているPDFを表示しようとすると、標準でパスワード入力フォームが表示されます。ここに正しいパスワードを入れると通常通りファイルを見ることができます。

パスワードがかかったファイルの場合

また、コード内でパスワードを入れることもできます。documentに対して.unlock(withPassword:)でパスワードを入れておくと、通常通りにPDFが表示されます。

        if let documentURL = Bundle.main.url(forResource: "sample01", withExtension: "pdf") {
            if let document = PDFDocument(url: documentURL) {
                document.unlock(withPassword: "password") // passwordの部分にパスワードを入れる
                pdfView.document = document
            }
        }

なお、PDFにパスワードが設定さているかどうかはdocument.isEncryptedで調べらることが可能です。

ドキュメントの操作 (PDFDocument)

PDFKitではPDFドキュメント自体の編集もすることができます。

ページの入れ替え

ドキュメントのページを入れ替えるには、documentの.exchangePage(at: withPageAt:)メソッドを呼び出します。

        if let documentURL = Bundle.main.url(forResource: "sample01", withExtension: "pdf") {
            if let document = PDFDocument(url: documentURL) {
                document.exchangePage(at: 0, withPageAt: 1) // 最初のページと次のページを入れ替える
                pdfView.document = document
            }
        }

ページ入れ替え

ページの削除

ドキュメントのページを削除するには、documentのdocument.removePage(at:)メソッドを呼び出します。

        if let documentURL = Bundle.main.url(forResource: "sample01", withExtension: "pdf") {
            if let document = PDFDocument(url: documentURL) {
                pdfView.displayMode = .twoUpContinuous
                document.removePage(at: 3) // indexが3のページを削除する
                pdfView.document = document
            }
        }

削除

ファイルの保存 (PDFDocument)

PDFDocumentにはファイ居るを書き込むためのwrite(to:)メソッドが存在します。引数には保存するファイルのパスを入れます。
以下はドキュメントディレクトリにoutput.pdfというファイルを書き出すサンプルです。(documentはPDFDocumentクラスです)

if let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
     let path = url.appendingPathComponent("output.pdf")
     document.write(to: path)
}

検索 (PDFDocument)

PDFKitでは、文字列でドキュメント内を検索できます。
一番簡単なのは、findString()メソッドでヒットしたPDFSelectionの一覧が返ってきます。

// [PDFSelection]の配列が返る。ヒット知ない時は空のリスト
let selections = findString("検索文字列", withOptions: .caseInsensitive)

また、beginFindString()、beginFindStrings()はPDFDocumentのDelegateでヒットする度にdidMatchString()が呼び出されます。 この2つの違いは検索文字列が複数指定するかしないかです。

        if let documentURL = Bundle.main.url(forResource: "sample01", withExtension: "pdf") {
            if let document = PDFDocument(url: documentURL) {
                document.delegate = self
                document.beginFindString("abc", withOptions: .caseInsensitive) // abcという文字列を検索
                pdfView.document = document
            }
        }
    }
    // 〜 中略 〜
 }
extension ViewController: PDFDocumentDelegate {
    // 検索にヒットしたら呼ばれる(引数はヒットした文字列のPDFSelection)
    public func didMatchString(_ instance: PDFSelection) {
    }

    // ドキュメントの検索終了
    public func documentDidEndDocumentFind(_ notification: Notification) {
    }
}

検索オプションについての詳細は、こちらをご覧ください。

さいごに

今回紹介した以外にも色々な機能があります。 次回、この記事に入れられなかった機能を紹介できればと思います。

参考