【swift】htmlをpdfデータとして保存しwebViewで表示&動的に書き換える

ディップAdventCalendar2017、3日目🌟٩( 'ω' )و


htmlで書いたデータをpdfで保存・作成し複数ページで表示する、
そして中身のデータを動的に書き換える方法です💪

最初はlibHaruという昔からあるpdf作成ライブラリから派生しましたswiftyHaruを使おうとしたのですが、
PDFGeneratorという(自分的に)もっと使いやすいライブラリを見つけました♪( ´▽`)

1.環境

  • Xcode9.1
  • swift3.2

  • podfileに以下を入力

pod 'PDFGenerator'

2.準備

  • .htmlを2つと.css1つを用意
  • .storyboard:viewControllerの上にwebViewを配置
  • webViewをIBOutlet接続

img1.png

  • htmlの中身を動的に差し替えるということで、htmlの中身はこのような形にしております☺️
  • #Data1##Data2#の部分を書き換えるようにします💪
    • なので最初に入れておくhtmlはテンプレデータの役目として使うイメージ
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="pdfSample.css">
  </head>
  <body>
    <p class="color">#Data1##Data2#</p>
  </body>
</html>

3.実装

■html表示用のwebViewを読み込む

pdfを作るにあたり先にhtmlを読み込んでおく必要があるので表示用のwebViewを作成しhtmlを読み込みますo(`ω´ )o

class pdfSampleViewController: UIViewController, UIWebViewDelegate {

    // webViewの親view
    @IBOutlet weak var sampleView: UIView!
    // webView
    @IBOutlet weak var webView: UIWebView!

    // pdfページ用(html読み込み用)のwebView
    var pdfPage1WebView = UIWebView()
    var pdfPage2WebView = UIWebView()
    // pdfページ用のwebView格納配列
    var webViewArray: [UIWebView] = []

    // MARK: - Life Cycle

    override func viewDidLoad() {
        super.viewDidLoad()

        let frameSize = CGRect(x: 0.0, y: 0, width: sampleView.bounds.width, height: sampleView.bounds.height / 2)
        // viewのサイズの初期化
        pdfPage1WebView = UIWebView(frame: frameSize)
        pdfPage2WebView = UIWebView(frame: frameSize)

        pdfPage1WebView.delegate = self
        pdfPage2WebView.delegate = self
    }
}

■htmlのデータを新たに作り保存する

上記で書いたように、テンプレデータに値を入れるため新たにhtmlデータを作成します。
書き換えたい値を別クラス内で辞書として持ちfor文で回すようにしています。
辞書のvalueの方をDBからデータを取ってくるというように変えると良いと思います☺️

// pdfのデータを表示する
func callingPDFData() {

    // テンプレートデータのパスを取得する
    if let pdfPage1Path = Bundle.main.path(forResource: "pdfPage1", ofType: "html"),
        let pdfPage2Path = Bundle.main.path(forResource: "pdfPage2", ofType: "html"),
        let cssFormatPath = Bundle.main.path(forResource: "pdfSample", ofType: "css") {

        // 編集後のデータを格納する
        var newPdfPage1Data = ""
        var newPdfPage2Data = ""
        var newPdfPage1FilePath = URL(fileURLWithPath: "")
        var newPdfPage2FilePath = URL(fileURLWithPath: "")

        do {
            // テンプレートデータの中身を取得する
            let pdfPage1Data = try NSString(contentsOf: URL(fileURLWithPath: pdfPage1Path), encoding: String.Encoding.utf8.rawValue)
            let pdfPage2Data = try NSString(contentsOf: URL(fileURLWithPath: pdfPage2Path), encoding: String.Encoding.utf8.rawValue)

            // 取得の中身を置換する
            newPdfPage1Data = pdfPage1Data.replacingOccurrences(of: "pdfSample.css", with: cssFormatPath)
            newPdfPage2Data = pdfPage2Data.replacingOccurrences(of: "pdfSample.css", with: cssFormatPath)
            for (key, value) in PdfDataModel().pdfPage1DataDictionary {
                newPdfPage1Data = newPdfPage1Data.replacingOccurrences(of: key, with: value)
            }
            for (key, value) in PdfDataModel().pdfPage2DataDictionary {
                newPdfPage2Data = newPdfPage2Data.replacingOccurrences(of: key, with: value)
            }

            // 置換後のデータをドキュメントディレクトリに保存する
            if let dir = FileManager.default.urls( for: .documentDirectory, in: .userDomainMask ).first {
                newPdfPage1FilePath = dir.appendingPathComponent("editPdfPage1.html")
                newPdfPage2FilePath = dir.appendingPathComponent("editPdfPage2.html")
                do {
                    try newPdfPage1Data.write(to: newPdfPage1FilePath, atomically: false, encoding: String.Encoding.utf8)
                    try newPdfPage2Data.write(to: newPdfPage2FilePath, atomically: false, encoding: String.Encoding.utf8)
                } catch {
                }
            }
        } catch {
        }
        // 新しく作ったデータでリクエストを投げる
        let req = NSMutableURLRequest(url: newPdfPage1FilePath, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 3)
        let req2 = NSMutableURLRequest(url: newPdfPage2FilePath, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 3)
        self.pdfPage1WebView.loadRequest(req as URLRequest)
        self.pdfPage2WebView.loadRequest(req2 as URLRequest)
    }
}
  • データ用クラス
class PdfDataModel {

    // 各データ変数を格納
    var pdfPage1DataDictionary: [String : String] = ["#Data1#": "HELLO",
                                                    "#Data2#": " WORLD"
    ]
    // 各データ変数を格納
    var pdfPage2DataDictionary: [String : String] = ["#Data1#": "hello",
                                                    "#Data2#": " world"
    ]
}

■htmlのデータをpdf内に保存する

html用のwebViewが読み込み完了したらpdfで保存する処理を行います💪

  • 読み込みが完了したか判別
// webViewカウント用配列
var webViewCountArray: [UIWebView] = []

// MARK: - UIWebViewDelegate

func webViewDidStartLoad(_ webView: UIWebView) {

    webViewCountArray.append(webView)
}

func webViewDidFinishLoad(_ webView: UIWebView) {

    webViewArray.append(webView)

    // htmlファイルが2ページ分とも読み込み完了したらpdfで表示する処理を呼ぶ
    if webViewArray.count == webViewCountArray.count {
        self.getPDFData()
        sampleView.backgroundColor = UIColor.lightGray
    }
}
  • webViewで読み込んだデータをpdfに格納していく
  • ライブラリ側のメソッドにpdfデータとして保存したいviewを継承したオブジェクトを渡すとそれを元にデータを作成してくれます💪💪💪便利!
import PDFGenerator

// pdfファイルの作成、保存
func getPDFData() {

    // ドキュメントディレクトリに保存する
    let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last

    if let pdfData = dir?.appendingPathComponent("pdfData.pdf", isDirectory: true) {
        // pdfファイルの作成、保存
        do {
            let data = try PDFGenerator.generated(by: webViewArray)
            try data.write(to: pdfData, options: .atomic)
        } catch {
        }
    }
}

■pdfを呼ぶ

  • pdfを呼び出す処理も同じくライブラリの処理を叩く💪
// 呼び出し
do {
    try PDFGenerator.generate(webViewArray, to: pdfData)
   } catch {
   }

   webView.scalesPageToFit = true
   webView.loadRequest(NSMutableURLRequest(url: pdfData) as URLRequest)

3.確認

  • デバックで見てみます。

スクリーンショット 2017-12-03 7.35.59.png

出来た!☺️✨✨


以下コード全体です↓

import UIKit
import PDFGenerator

class pdfSampleViewController: UIViewController, UIWebViewDelegate {

    // webViewの親view
    @IBOutlet weak var sampleView: UIView!
    // webView
    @IBOutlet weak var webView: UIWebView!

    // pdfページ用(html読み込み用)のwebView
    var pdfPage1WebView = UIWebView()
    var pdfPage2WebView = UIWebView()
    // webViewカウント用配列
    var webViewCountArray: [UIWebView] = []
    // pdfページ用のwebView格納配列
    var webViewArray: [UIWebView] = []

    // MARK: - Life Cycle

    override func viewDidLoad() {
        super.viewDidLoad()

        let frameSize = CGRect(x: 0.0, y: 0, width: sampleView.bounds.width, height: sampleView.bounds.height / 2)
        // viewのサイズの初期化
        pdfPage1WebView = UIWebView(frame: frameSize)
        pdfPage2WebView = UIWebView(frame: frameSize)

        pdfPage1WebView.delegate = self
        pdfPage2WebView.delegate = self

        self.callingPDFData()
    }

    // MARK: - UIWebViewDelegate

    func webViewDidStartLoad(_ webView: UIWebView) {

        webViewCountArray.append(webView)
    }

    func webViewDidFinishLoad(_ webView: UIWebView) {

        webViewArray.append(webView)

        // htmlファイルが2ページ分とも読み込み完了したらpdfで表示する処理を呼ぶ
        if webViewArray.count == webViewCountArray.count {
            self.getPDFData()
            sampleView.backgroundColor = UIColor.lightGray
        }
    }

    // MARK: - Private Method

    // pdfファイルの作成、保存
    func getPDFData() {

        let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last
        if let pdfData = dir?.appendingPathComponent("pdfData.pdf", isDirectory: true) {
            // pdfファイルの作成、保存
            do {
                let data = try PDFGenerator.generated(by: webViewArray)
                try data.write(to: pdfData, options: .atomic)
            } catch {
            }
            // 呼び出し
            do {
                try PDFGenerator.generate(webViewArray, to: pdfData)
            } catch {
            }

            webView.scalesPageToFit = true
            webView.loadRequest(NSMutableURLRequest(url: pdfData) as URLRequest)
        }
    }

    // pdfのデータを表示する
    func callingPDFData() {

        // デフォルトフォーマットのパスを取得する
        if let pdfPage1Path = Bundle.main.path(forResource: "pdfSample1", ofType: "html"),
            let pdfPage2Path = Bundle.main.path(forResource: "pdfSample2", ofType: "html"),
            let cssFormatPath = Bundle.main.path(forResource: "pdfSample", ofType: "css") {

            // 編集後のデータを格納する
            var newPdfPage1Data = ""
            var newPdfPage2Data = ""
            var newPdfPage1FilePath = URL(fileURLWithPath: "")
            var newPdfPage2FilePath = URL(fileURLWithPath: "")

            do {
                // デフォルトフォーマットの中身を取得する
                let pdfPage1TemplateData = try NSString(contentsOf: URL(fileURLWithPath: pdfPage1Path), encoding: String.Encoding.utf8.rawValue)
                let pdfPage2TemplateData = try NSString(contentsOf: URL(fileURLWithPath: pdfPage2Path), encoding: String.Encoding.utf8.rawValue)

                // 取得の中身を置換する
                newPdfPage1Data = pdfPage1TemplateData.replacingOccurrences(of: "pdfSample.css", with: cssFormatPath)
                newPdfPage2Data = pdfPage2TemplateData.replacingOccurrences(of: "pdfSample.css", with: cssFormatPath)
                for (key, value) in pdfDataModel().pdfPage1DataDictionary {
                    newPdfPage1Data = newPdfPage1Data.replacingOccurrences(of: key, with: value)
                }
                for (key, value) in pdfDataModel().pdfPage2DataDictionary {
                    newPdfPage2Data = newPdfPage2Data.replacingOccurrences(of: key, with: value)
                }

                // 置換後のデータをドキュメントディレクトリに保存する
                if let dir = FileManager.default.urls( for: .documentDirectory, in: .userDomainMask ).first {
                    newPdfPage1FilePath = dir.appendingPathComponent("editPdfPage1.html")
                    newPdfPage2FilePath = dir.appendingPathComponent("editPdfPage2.html")
                    // htmlファイルの作成、保存
                    do {
                        try newPdfPage1Data.write(to: newPdfPage1FilePath, atomically: false, encoding: String.Encoding.utf8)
                        try newPdfPage2Data.write(to: newPdfPage2FilePath, atomically: false, encoding: String.Encoding.utf8)
                    } catch {
                    }
                }
            } catch {
            }
            // 新しく作ったデータでリクエストを投げる
            let req = NSMutableURLRequest(url: newPdfPage1FilePath, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 3)
            let req2 = NSMutableURLRequest(url: newPdfPage2FilePath, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 3)
            self.pdfPage1WebView.loadRequest(req as URLRequest)
            self.pdfPage2WebView.loadRequest(req2 as URLRequest)
        }
    }
}

class pdfDataModel {

    // 各データ変数を格納
    var pdfPage1DataDictionary: [String : String] = ["#Data1#": "HELLO",
                                                    "#Data2#": " WORLD"
    ]
    // 各データ変数を格納
    var pdfPage2DataDictionary: [String : String] = ["#Data1#": "hello",
                                                    "#Data2#": " world"
    ]
}