webentwicklung-frage-antwort-db.com.de

Generieren PDF mit Bildern aus HTML in Swift ohne Anzeige der Druckoberfläche

Ich generiere in meiner Swift-Anwendung eine PDF aus etwas HTML. Ich verwende eine UIMarkupTextPrintFormatter und habe einen ähnlichen Code wie this Gist . Ich erhalte das PDF als NSData und füge es an eine E-Mail an. Die App zeigt dem Benutzer vor dem Anhängen nicht die PDF an.

Ich möchte jetzt einige Bilder einfügen. Das Hinzufügen ihrer NSURL in HTML mit meiner aktuellen PDF - Erzeugungsstrategie funktioniert nicht. Wie kann ich NSData von einem PDF erhalten, das meinem HTML-Code mit hinzugefügten Bildern entspricht? Hier sind einige Dinge, die ich ausprobiert habe:

  1. Diese Antwort schlägt vor, das base64-Bild in HTML einzubetten und UIPrintInteractionController zu verwenden. Dies gibt mir eine Druckvorschau mit korrekt eingebetteten Bildern, aber wie gehe ich von dort zu NSData entsprechend der Ausgabe von PDF?

  2. Ich habe einige ähnliche Vorschläge durchlaufen, die UIWebView durchlaufen haben, aber diese führen zu demselben Problem - ich möchte dem Benutzer keine Vorschau anzeigen.

27
Hélène Martin

Die UIMarkupTextPrintFormatter scheint den html img-Tag nicht zu unterstützen. Die Dokumentation von Apple ist hier nicht sehr informativ. Sie besagt lediglich, dass der Initialisierungsparameter "Der HTML-Markup-Text für den Druckformatierer" ist. Es gibt keinen Hinweis darauf, welche Tags vom Druckformatierer genau unterstützt werden. 

Nach vielen Tests kann ich nur feststellen, dass UIMarkupTextPrintFormatterNICHT das Anzeigen von Bildern unterstützt.

Also, wo bleiben Menschen, die die Bequemlichkeit der Erstellung von PDFs aus HTML-Inhalten wünschen? 

Die einzige Möglichkeit, dies zu erreichen, besteht also darin, eine verborgene Webansicht zu verwenden, in die Sie Ihren HTML-Inhalt laden, und dann die UIViewPrintFormatter der Webansicht verwenden. Das funktioniert aber fühlt sich wirklich wie ein Hack an. 

Es funktioniert und es werden Bilder in Ihr PDF -Dokument eingebettet. Wenn ich es wäre, würde ich mich auf die Verwendung von CoreText und Quartz 2D beschränken, da Sie sonst viel mehr Kontrolle über die PDF-Erzeugung hätten Nachdem ich gesagt habe, dass es ein Overkill ist, kenne ich die Größe oder Komplexität Ihres HTML-Inhalts nicht. 

Also zu einem Arbeitsbeispiel ...

Konfiguration

Es war nützlich, eine Basis-URL zu definieren, damit ich die Dateinamen der Bilder, die ich verwenden wollte, einfach weitergeben konnte. Die Basis-URL, die einem Verzeichnis im App-Bundle zugeordnet ist, in dem sich die Bilder befinden. Sie können auch Ihren eigenen Standort definieren.

Bundle.main.resourceURL + "www/"

 Copy Files Build Phase

Dann habe ich ein Protokoll erstellt, um dokumentbezogene Funktionen zu behandeln. Standardimplementierungen werden von einer Erweiterung bereitgestellt, wie Sie im folgenden Code sehen können.

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
    }

}

Dann ließ ich dieses Protokoll von einem View-Controller übernehmen. Der Schlüssel für diese Arbeit ist hier, Ihr View Controller muss die UIWebViewDelegate übernehmen und in der func webViewDidFinishLoad(_ webView: UIWebView) können Sie sehen, dass das PDF erstellt wurde.

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


    }


}
25
Pete Hornsby

Mit Teilen aus fragilecat seiner Antwort habe ich ein Beispielprojekt mit drei Viewcontrollern zusammengestellt:

  • Der erste stellt lokales HTML mit einem Bild mit WKWebView dar und exportiert dann Nach PDF
  • Im zweiten wird das PDF mit einem anderen WKWebView dargestellt
  • Der dritte zeigt den Druckdialog

https://github.com/bvankuik/TestMakeAndPrintPDF

7
Bart van Kuik

Meine vielen Stunden, die ich mit diesem Problem verschwendet habe, sagen mir, dass UIMarkupTextPrintFormatter Bilder unterstützt . Zwei Gründe dafür:

  1. Wie Sie sagen, zeigt das von UIPrintInteractionController mit einem UIMarkupTextPrintFormatter gezeigte PDF) die Bilder korrekt an.
  2. Das folgende Tutorial (auch in den Kommentaren erwähnt) schafft es tatsächlich, mit UIMarkupTextPrintFormatter ein PDF) mit Bildern zu erstellen. Ich habe den Grund untersucht und gefunden Dafür wurde der HTML-Code zuvor in ein UIWebView geladen. Es sieht so aus, als ob UIMarkupTextPrintFormatter auf eine WebKit Komponente angewiesen ist, um seine Bilder zu rendern.

Ich bin mir bewusst, dass ich keine Lösung anbiete, aber für mich ist dies eindeutig ein iOS-Fehler. Ich habe kein iOS 11, daher wurde es möglicherweise in dieser kommenden Version behoben.

Ich habe den Fehler ausführlich beschrieben hier zusammen mit einer Beispiel-App, die die Erstellung von PDFs mit den verschiedenen verfügbaren Druckformaten ermöglicht.

NB: Ich habe es nur geschafft, Base64- und "externe" (d. H. http://example.com/my-image.png ) Bilder zum Laufen zu bringen.

7
nyg

Ersetzen

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

mit

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

dabei ist wkWebView Ihre Instanz von WKWebView, in die Sie zuvor den HTML-Inhalt htmlContent geladen haben, der ein Bild enthält, und printPageRenderer Ihre Instanz von UIPrintPageRenderer.

3
fivewood

Wie viele Menschen hier verbrachte ich Stunden damit, das Problem zu lösen. Ich habe Basis-64-Bilder verwendet, keine externen Bilder. Folgendes funktioniert für mich mit Bildern, die im <img src="data:image/jpeg;base64,..." /> html-Tag geladen sind, und auch mit css mit background-image: "url('data:image/jpeg;base64,...)".

Hier ist was ich endete mit:

  • Bei der ursprünglichen Frage wurde der Webview vor dem Generieren der PDF-Datei nicht angezeigt. Ich brauchte das nicht, aber wenn Sie es einfach unsichtbar machen, wie @Pete Hornsby (akzeptierte Antwort) vorschlägt.
  • Wie @fivewood erwähnt benutze nicht UIMarkupTextPrintFormatter sondern webPreview.viewPrintFormatter(), ich habe keine Ahnung warum, aber es funktioniert so.
  • Required: Warten Sie, bis der Webview geladen ist, bevor Sie PDF ( hier ) erzeugen.
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

Erstellen Sie zuerst die UIWebView-Instanz

var webView:UIWebView?

Danach erstellen Sie die Funktion für init webView und fügen UIWebViewDelegate im Controller hinzu.

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

Danach schreiben Sie PDF Conversion-Code webViewDidFinishLoad-Methode

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