web-dev-qa-db-ja.com

PDF HTMLの画像を使用してSwift印刷インターフェイスを表示せずに生成

HTMLからSwiftアプリケーションでPDFを生成します。 UIMarkupTextPrintFormatterを使用し、 this Gist に似たコードを持っています。 PDFをNSDataとして取得し、メールに添付します。アプリは、アタッチする前にユーザーにPDFを表示しません。

画像を含めたいと思います。現在のPDF生成戦略にHTMLでNSURLを追加しても機能しません。画像が追加されたHTMLに対応するPDFのNSDataを取得するにはどうすればよいですか?ここに私が試したものがいくつかあります:

  1. この回答 は、HTMLにbase64画像を埋め込み、 UIPrintInteractionController を使用することを提案しています。これにより、画像が正しく埋め込まれた印刷プレビューが表示されますが、そこからPDF出力に対応するNSDataに移動するにはどうすればよいですか?

  2. UIWebViewを通過するいくつかの同様の提案を見てきましたが、それらは同じ問題につながります。ユーザーにプレビューを表示したくありません。

30
Hélène Martin

UIMarkupTextPrintFormatter は、html imgタグをサポートしていないようです。 Appleのドキュメントはここではあまり有益ではなく、単に初期化パラメーターが "印刷フォーマッターのHTMLマークアップテキスト"であると述べています。印刷フォーマッタでサポートされているタグを正確に示すものはありません。

多くのテストの後、私が描くことができる唯一の結論は、 UIMarkupTextPrintFormatter[〜#〜] not [〜#〜]画像の表示をサポートします。

それでは、HTMLコンテンツからPDFを作成する便利さを望む人々はどこにいるのでしょうか?

したがって、この作業を行うために私が見つけた唯一の方法は、htmlコンテンツをロードする非表示のWebビューを使用してから、Webビューの UIViewPrintFormatter を使用することです。これは機能しますが、実際にはハッキングのように感じます。

それは動作し、画像をPDFドキュメントに埋め込みますが、私だったら CoreText および Quartz 2D pdf生成プロセスをはるかに制御できるので、やり過ぎかもしれないと私は理解しているが、htmlコンテンツのサイズや複雑さはわかりません。

それでは実際の例に...

セットアップ

使用したい画像のファイル名だけを渡すことができるように、ベースURLを定義すると便利でした。画像が配置されているアプリバンドル内のディレクトリにマッピングされたベースURL。独自の場所も定義できます。

_Bundle.main.resourceURL + "www/"
_

Copy Files Build Phase

次に、ドキュメント関連の機能を処理するプロトコルを作成しました。以下のコードでわかるように、デフォルトの実装は拡張機能によって提供されます。

_protocol DocumentOperations {

    // Takes your image tags and the base url and generates a html string
    func generateHTMLString(imageTags: [String], baseURL: String) -> String

    // Uses UIViewPrintFormatter to generate pdf and returns pdf location
    func createPDF(html: String, formmatter: UIViewPrintFormatter, filename: String) -> String

    // Wraps your image filename in a HTML img tag
    func imageTags(filenames: [String]) -> [String]
}


extension DocumentOperations  {

    func imageTags(filenames: [String]) -> [String] {

        let tags = filenames.map { "<img src=\"\($0)\">" }

        return tags
    }


    func generateHTMLString(imageTags: [String], baseURL: String) -> String {

        // Example: just using the first element in the array
        var string = "<!DOCTYPE html><head><base href=\"\(baseURL)\"></head>\n<html>\n<body>\n"
        string = string + "\t<h2>PDF Document With Image</h2>\n"
        string = string + "\t\(imageTags[0])\n"
        string = string + "</body>\n</html>\n"

        return string
    }


    func createPDF(html: String, formmatter: UIViewPrintFormatter, filename: String) -> String {
        // From: https://Gist.github.com/nyg/b8cd742250826cb1471f

        print("createPDF: \(html)")

        // 2. Assign print formatter to UIPrintPageRenderer
        let render = UIPrintPageRenderer()
        render.addPrintFormatter(formmatter, startingAtPageAt: 0)

        // 3. Assign paperRect and printableRect
        let page = CGRect(x: 0, y: 0, width: 595.2, height: 841.8) // A4, 72 dpi
        let printable = page.insetBy(dx: 0, dy: 0)

        render.setValue(NSValue(cgRect: page), forKey: "paperRect")
        render.setValue(NSValue(cgRect: printable), forKey: "printableRect")

        // 4. Create PDF context and draw
        let pdfData = NSMutableData()
        UIGraphicsBeginPDFContextToData(pdfData, CGRect.zero, nil)

        for i in 1...render.numberOfPages {

            UIGraphicsBeginPDFPage();
            let bounds = UIGraphicsGetPDFContextBounds()
            render.drawPage(at: i - 1, in: bounds)
        }

        UIGraphicsEndPDFContext();

        // 5. Save PDF file
        let path = "\(NSTemporaryDirectory())\(filename).pdf"
        pdfData.write(toFile: path, atomically: true)
        print("open \(path)")

        return path
    }

}
_

その後、このプロトコルをView Controllerで採用しました。この作業を行う鍵はここにあり、View ControllerはUIWebViewDelegateを採用する必要があり、func webViewDidFinishLoad(_ webView: UIWebView)でpdfが作成されていることを確認できます。

_class ViewController: UIViewController, DocumentOperations {    
    @IBOutlet private var webView: UIWebView!

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        webView.delegate = self
        webView.alpha = 0

        if let html = prepareHTML() {
            print("html document:\(html)")
            webView.loadHTMLString(html, baseURL: nil)

        }
    }

    fileprivate func prepareHTML() -> String? {

        // Create Your Image tags here
        let tags = imageTags(filenames: ["PJH_144.png"])
        var html: String?

        // html
        if let url = Bundle.main.resourceURL {

            // Images are stored in the app bundle under the 'www' directory
            html = generateHTMLString(imageTags: tags, baseURL: url.absoluteString + "www/")
        }

        return html
    }
}


extension ViewController: UIWebViewDelegate {

    func webViewDidFinishLoad(_ webView: UIWebView) {
        if let content = prepareHTML() {
            let path = createPDF(html: content, formmatter: webView.viewPrintFormatter(), filename: "MyPDFDocument")
            print("PDF location: \(path)")
        }


    }


}
_
27
Pete Hornsby

彼の答えのfragilecatのパーツを使用して、Swift 5:

  • 最初のものは、WKWebViewを使用して1つの画像でローカルHTMLをレンダリングし、PDFにエクスポートします
  • 2番目のものは、別のWKWebViewでPDFをレンダリングします
  • 3番目は印刷ダイアログを示しています

https://github.com/bvankuik/TestMakeAndPrintPDF

7
Bart van Kuik

この問題に費やされた私の多くの時間は、UIMarkupTextPrintFormatterdoesサポート画像を教えてくれます。これには2つの理由があります。

  1. あなたが言うように、PDFはUIPrintInteractionControllerUIMarkupTextPrintFormatterで示され、画像を正しく表示します。
  2. 次の tutorial (コメントにも記載されています)は、実際にUIMarkupTextPrintFormatterを使用してPDFを使用してイメージを作成します。これは、HTMLコードが事前にUIWebViewにロードされていたためで、UIMarkupTextPrintFormatterはその画像をレンダリングするためにWebKitコンポーネントに依存しているようです。

ソリューションを提供していないことは承知していますが、これは明らかにiOSのバグです。私はiOS 11を持っていないので、多分それはこの次のバージョンで解決されました。

バグを詳細に説明しました here 利用可能なさまざまな印刷フォーマッタを使用してPDFを作成できるサンプルアプリと共に。

NB:Base64と "external"(つまり http://example.com/my-image.png )の画像のみが機能するようになりました。

7
nyg

交換

let printFormatter = UIMarkupTextPrintFormatter(markupText: htmlContent)
printPageRenderer.addPrintFormatter(printFormatter, startingAtPageAt: 0)

let printFormatter = wkWebView.viewPrintFormatter()
printPageRenderer.addPrintFormatter(printFormatter, startingAtPageAt: 0)

wkWebViewは、画像を含むHTMLコンテンツWKWebViewを以前に読み込んだhtmlContentのインスタンスであり、printPageRendererUIPrintPageRendererのインスタンスです。

4
fivewood

まず、UIWebViewインスタンスを作成します

var webView:UIWebView?

その後、init webViewの関数を作成し、コントローラーにUIWebViewDelegateを追加します。

func initWebView(htmlString:String) {
  self.webView = UIWebView.init(frame:self.view.frame)
  self.webView?.delegate = self
  self.webView?.loadHTMLString(htmlString, baseURL: nil)
}

その後書き込みPDF変換コードwebViewDidFinishLoadメソッド

func webViewDidFinishLoad(_ webView: UIWebView){
    if(webView.isLoading){
        return
    }
    let render = UIPrintPageRenderer()
    render.addPrintFormatter((self.webView?.viewPrintFormatter())!, startingAtPageAt: 0)

    //Give your needed size

    let page = CGRect(x: 0, y: 0, width: 384, height: 192)

    render.setValue(NSValue(cgRect:page),forKey:"paperRect")
    render.setValue(NSValue(cgRect:page), forKey: "printableRect")

    let pdfData = NSMutableData()
    UIGraphicsBeginPDFContextToData(pdfData,page, nil)

    for i in 1...render.numberOfPages-1{

        UIGraphicsBeginPDFPage();
        let bounds = UIGraphicsGetPDFContextBounds()
        render.drawPage(at: i - 1, in: bounds)
    }

    UIGraphicsEndPDFContext();

    //For locally view page

     let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
     let fileURL = documentsDirectory.appendingPathComponent("file.pdf");
     if !FileManager.default.fileExists(atPath:fileURL.path) {
         do {
           try pdfData.write(to: fileURL)
           print("file saved")

         } catch {
             print("error saving file:", error);
       }
     }
}

ここの多くの人々のように、私はこれを解決するのに時間を無駄にしました。外部イメージではなく、base-64イメージを使用しました。以下は、_<img src="data:image/jpeg;base64,..." />_ htmlタグにロードされた画像とbackground-image: "url('data:image/jpeg;base64,...)"を使用したcssでうまく機能します。

ここに私が終わったものがあります:

  • 元の質問では、PDFを生成する前にWebビューを表示する必要はありませんでした。私はそれを必要としませんでしたが、 @ Pete Hornsby(受け入れられた答え) が示唆するようにそれを見えなくするだけなら。
  • @ fivewoodが述べたようにUIMarkupTextPrintFormatterを使用せず、代わりにwebPreview.viewPrintFormatter()を使用します。理由はわかりませんが、そのように動作します。
  • 必須:WebViewがロードされるのを待ってから、PDF( ここにポイント) )。
_let renderer = UIPrintPageRenderer()

// A4 in portrait mode
let pageFrame = CGRect(x: 0.0, y: 0.0, width: 595.2, height: 841.8)
renderer.setValue(NSValue(cgRect: pageFrame), forKey: "paperRect")

// webview here is a WKWebView instance in which we previously loaded the page
renderer.addPrintFormatter(webview.viewPrintFormatter(), startingAtPageAt: 0)

let pdfData = NSMutableData()

UIGraphicsBeginPDFContextToData(pdfData, CGRect.zero, nil)
for i in 0..<renderer.numberOfPages {
    UIGraphicsBeginPDFPage()
    renderer.drawPage(at: i, in: UIGraphicsGetPDFContextBounds())
}

UIGraphicsEndPDFContext()

// DONE: pdfData contains the pdf data with images.
// The following tutorial (mentioned several times in this thread) shows
// how to store it in a local file or send it by mail:
// https://www.appcoda.com/pdf-generation-ios/

_
0
Cinn