Blog スタッフブログ

Swift システム開発

[Swift]PDFKitで表示したPDFへ画像を追加し書き出す

はじめまして、株式会社MIXシステム開発担当のBloomです。

早速本題のSwiftで利用できるフレームワーク、PDFKitを利用しPDF書類内に画像を追加する処理について、

お仕事の中で得た知見を共有させていただきたいと思います。

PDFKitとは

PDFKitはiOS11から追加されたPDF操作のためのフレームワークです。

PDFKitの公式ドキュメントはこちら

PDFファイルを表示するのみの最小コードは以下の形となります。サンプルコードを掲載します。

import PDFKit

class ViewController: UIViewController {
    // storyboard上で設定したPDFView
    @IBOutlet weak var pdfView: PDFView!
    override func viewDidLoad() {
        super.viewDidLoad()
        // ローカルのリソースを参照する場合
        if let url = Bundle.main.url(forResource: "sample", withExtension: "pdf") {
            if let document = PDFDocument(url: url) {
                pdfView.document = document
            }
        }
        // Dataから直接PDFを生成する場合
        if let document = PDFDocument(data: : data) {
            pdfView.document = document
        }
    }
}

実行結果

公式のドキュメントをPDFでダウンロードしてサンプルにしたけど分かりにくかったな…

簡単にPDFの表示ができました。

PDFDocumentはURLからの初期化のほか、Dataから直接の初期化もできるのでネットワーク経由でダウンロードしたPDFを扱う場合はそちらを利用するケースも多いでしょう。

PDFKitで表示したPDFViewへ画像を追加する

さて、表示したPDFへ画像の追加を行う処理を組み込みましょう。

まずPDFKitで行えるPDFの編集手段を挙げていきます。

例えば編集対象のページを一旦UIImageとして書き出してしまい、手書き内容と結合したUIImageをまたPDFとして書き出しPDFDocumentへinsert(_ page: PDFPage, at index: Int)メソッドを利用してページとして追加する方法がありますが、この方法ではPDFページ内に含まれる文字情報が画像化に伴い完全に消えてしまうといったデメリットがあります。

それを回避するために、今回はPDFの注釈機能を利用して画像を追加する方法を紹介します。

PDFの注釈を利用するためにはPDFAnnotationクラスを利用します。そのままではコードから画像付きの注釈を生成できないので、次のサブクラスを作成してください。

class ImageAnnotation: PDFAnnotation {
    var image: UIImage!
    
    init(with image: UIImage!, forBounds bounds: CGRect, withProperties properties: [AnyHashable : Any]?) {
        super.init(bounds: bounds, forType: .stamp, withProperties: properties)
        self.image = image
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func draw(with box: PDFDisplayBox, in context: CGContext) {
        guard let cgImage = self.image.cgImage else { return }
        context.draw(cgImage, in: self.bounds)
    }
}

このImageAnnotationクラスではPDFAnnotationクラスの描画を担当するdraw(with box: PDFDisplayBox, in context: CGContext)メソッドをオーバーライドすることでコード上で指定した画像をPDF上に描画できるようにカスタマイズしています。

この注釈をPDFPageへ追加しましょう。

// 現在開いているページを取得
if let page = pdfView.currentPage,
   let image = UIImage(named: "image.png") {
    // 対象のページのサイズをCGRectで取得
    let pageBounds = page.bounds(for: .cropBox)
    // 中央部に座標を指定
    let imageStamp = ImageAnnotation(with: image, forBounds: CGRect(x: pageBounds.width / 2, y: pageBounds.height / 2, width: 161, height: 67), withProperties: nil)
    // 対象のページへ注釈を追加
    page.addAnnotation(imageStamp)
}

実行結果

弊社のロゴを拝借しました

指定した矩形範囲へ画像を配置することができました。座標系がUIViewとは異なるので気をつけましょう。

最後に、編集したPDFの書き出し処理はPDFDocumentクラスのdataRepresentationメソッドからDataを抽出、お馴染みのwrite(_:to)メソッドを利用して行います。

if let data = pdfView.document?.dataRepresentation() {
    let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
    let path = URL(fileURLWithPath: documentsPath, isDirectory: true).appendingPathComponent("pdf").path
    if !FileManager.default.fileExists(atPath: path){
        try! FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
    }
    let filename = "export.pdf"
    let destination =  URL(fileURLWithPath: path, isDirectory: true).appendingPathComponent(filename)
    try? data.write(to: destination)
}

これでDocuments/pdfディレクトリ配下に注釈を追加したPDFが書き出されました。良かったですね。

謝辞

未経験からシステム開発に携わり8年の歳月が経ちましたが、分からないことや知りたいことがあったとき、技術ブログを書いていただいていた先達の皆様のおかげで答えに辿り着けたことが多々ありました。

自分もそれに倣って得た知見を出力していくことである種の恩返しができるように励んでいきたいと思います。

これからも定期的に更新していくため応援をよろしくお願いします。

参考文献

PDFKit – Apple Developer Documentation

Adding Custom Graphics to a PDF – Apple Developer Documentation

[iOS 11] PDFKit入門 – DevelopersIO

how to add image annotation in PDFKit in iOS11 – StackOverflow