36 votes

Générez un PDF avec des images à partir de HTML dans Swift sans afficher l'interface d'impression

- Je générer un PDF dans mon Swift demande de code HTML. J'utilise un UIMarkupTextPrintFormatter et ont un code similaire à ce gist. Je reçois le fichier PDF en NSData et le joindre à un e-mail. L'application n'affiche pas le fichier PDF de l'utilisateur avant de l'attacher.

Maintenant, j'aimerais inclure des images. L'ajout de leur NSURL en HTML avec mon actuelle génération des PDF stratégie ne fonctionne pas. Comment puis-je obtenir de l' NSData d'un fichier PDF correspondant à ma HTML avec des images ajoutées? Voici certaines choses que j'ai essayé:

  1. Cette réponse suggère l'incorporation de la base64 image dans le HTML et l'utilisation de UIPrintInteractionController. Cela me donne un aperçu avant impression avec correctement des images intégrées, mais comment dois-je aller à partir de là à l' NSData correspondant à la sortie PDF?

  2. J'ai vu certaines des suggestions similaires traverse UIWebView , mais ceux qui conduisent à la même question -- je ne veux pas afficher un aperçu de l'utilisateur.

33voto

fragilecat Points 2030

L' UIMarkupTextPrintFormatter ne semblent pas soutenir le html img balise. La documentation d'Apple n'est pas très informatif ici, il précise simplement que l'initialisation du paramètre est "Le balisage HTML de texte pour l'impression de formateur". Il n'y a aucune indication de ce que les balises sont pris en charge par l'impression de formateur.

Après de nombreux tests à la seule conclusion que je peux en tirer est qu' UIMarkupTextPrintFormatter ne prend PAS en charge l'affichage des images.

D'où vient donc que de laisser les gens qui veulent la commodité de la création de PDF à partir du contenu HTML?

Donc le seul moyen que j'ai trouvé pour faire ce travail est d'utiliser un caché de la vue web que vous chargez votre contenu html, puis utiliser le web en vue de l' UIViewPrintFormatter. Cela fonctionne, mais se sent vraiment comme un hack.

Il fonctionne bien et il va incorporer des images dans votre document PDF, mais si c'était moi, je pencherais vers l'utilisation de CoreText et de Quartz 2D que vous avez beaucoup plus de contrôle sur le processus de génération de pdf, après avoir dit que je comprends, il serait peut-être exagéré, je ne sais pas la taille ou la complexité de votre contenu html.

Pour un exemple de travail...

Le programme d'installation

Il est utile de définir une url de base pour que je puisse simplement passer dans les noms de fichiers des images, j'ai voulu l'utiliser. L'url de base est mappé à un répertoire dans l'app bundle où les images se trouvent. Vous pouvez définir votre propre emplacement.

Bundle.main.resourceURL + "www/"

Copy Files Build Phase

Puis j'ai créé un protocole de gérer des fonctionnalités similaires. Des implémentations par défaut sont fournis par une extension comme vous pouvez le voir dans le code ci-dessous.

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
    }

}

Ensuite, j'ai eu ce protocole adopté par une-vue-contrôleur. La clé pour faire ce travail, c'est là, votre point de vue contrôleur doit adopter l' UIWebViewDelegate et dans le func webViewDidFinishLoad(_ webView: UIWebView) vous pouvez voir le fichier pdf est créé.

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


    }


}

12voto

Bart Doe Points 703

En utilisant des pièces de fragilecat sa réponse, j'ai mis en place un exemple de projet avec trois viewcontrollers:

  • Le premier rend le HTML local avec une image à l'aide de WKWebView, puis exporte au format PDF
  • Le second rend le PDF avec un autre WKWebView
  • Le troisième montre la boîte de dialogue d'impression

https://github.com/bvankuik/TestMakeAndPrintPDF

10voto

fivewood Points 109

Remplacer

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

avec

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

wkWebView est votre instance de WKWebView dans laquelle vous avez précédemment chargé le contenu HTML htmlContent qui contient une image et printPageRenderer est votre instance de UIPrintPageRenderer .

7voto

nyg Points 1127

Mes nombreuses heures perdues sur cette question, me dit UIMarkupTextPrintFormatter ne prend en charge les images. Deux raisons à cela:

  1. Comme vous le dites, le fichier PDF illustré par UIPrintInteractionController avec un UIMarkupTextPrintFormatter montre les images correctement.
  2. La suite de tutoriel (également mentionné dans les commentaires) gère pour créer un fichier PDF avec des images à l'aide d' UIMarkupTextPrintFormatter. Je l'ai étudié et trouvé la raison pour cela est que le code HTML a été chargé au préalable dans un UIWebView. Il ressemble UIMarkupTextPrintFormatter s'appuie sur certains WebKit de la composante pour le rendu de ses images.

Je suis conscient que je ne suis pas fournir de solution mais pour moi c'est clairement un iOS bug. Je n'ai pas iOS 11 alors peut-être il a été résolu dans la prochaine version.

J'ai décrit le bug en détail ici avec un exemple d'application qui permet de créer des fichiers Pdf à l'aide de l'impression différents formateurs disponibles.

NB: je n'ai réussi à obtenir en Base64 et "externe" (c'est à dire http://example.com/my-image.pngles images de travail.

1voto

Tout d'abord, créez une instance UIWebView

 var webView:UIWebView?
 

Après cela, créez la fonction pour init webView et ajoutez UIWebViewDelegate dans le contrôleur,

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

Après cela, écrivez le code de conversion PDF méthode 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);
       }
     }
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X