Je génère un PDF dans mon application Swift
à partir de HTML. J'utilise un UIMarkupTextPrintFormatter
et ai un code similaire à this Gist . Je reçois le PDF en tant que NSData
et le joint à un courrier électronique. L'application ne montre pas le PDF à l'utilisateur avant de le joindre.
J'aimerais maintenant inclure quelques images. Ajouter leur NSURL
en HTML avec ma stratégie de génération PDF actuelle ne fonctionne pas. Comment puis-je obtenir NSData
d'un PDF correspondant à mon code HTML avec des images ajoutées? Voici quelques choses que j'ai essayées:
Cette réponse suggère d’incorporer l’image base64 dans le code HTML et d’utiliser UIPrintInteractionController
. Cela me donne un aperçu avant impression avec des images correctement intégrées, mais comment puis-je y aller à NSData
correspondant à la sortie PDF?
J'ai vu des suggestions similaires passer par UIWebView
mais celles-ci mènent au même problème - je ne veux pas montrer un aperçu à l'utilisateur.
Le UIMarkupTextPrintFormatter
ne semble pas supporter la balise html img
. La documentation d'Apple est peu informative ici, elle indique simplement que le paramètre d'initialisation est "Le texte de balisage HTML pour le formateur d'impression". Rien n’indique exactement quelles étiquettes sont supportées par le formateur d’impression.
Après de nombreux tests, la seule conclusion que je puisse tirer est que UIMarkupTextPrintFormatter
ne N'EST PAS supporte l'affichage d'images.
Alors, où cela laisse-t-il les gens qui veulent avoir la possibilité de créer des PDF à partir de contenu HTML?
Ainsi, le seul moyen que j’ai trouvé pour que cela fonctionne est d’utiliser une vue Web masquée dans laquelle vous chargez votre contenu HTML, puis utilisez le UIViewPrintFormatter
de la vue Web. Cela fonctionne mais ressemble vraiment à un hack.
Cela fonctionne et incorporera des images dans votre document PDF. Cependant, si c'était moi, je préférerais utiliser CoreText et Quartz 2D car vous auriez beaucoup plus de contrôle sur la génération de pdf. processus, après avoir dit que je comprenais que c’était peut-être excessif, je ne connaissais pas la taille ni la complexité de votre contenu HTML.
Passons à un exemple de travail ...
Installer
Il était utile de définir une URL de base afin que je puisse simplement passer dans les noms de fichiers des images que je voulais utiliser. L'URL de base est mappée sur un répertoire du groupe d'applications où se trouvent les images. Vous pouvez également définir votre propre emplacement.
Bundle.main.resourceURL + "www/"
Ensuite, j'ai créé un protocole pour gérer les fonctionnalités liées aux documents. Les implémentations par défaut sont fournies 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 fait adopter ce protocole par un contrôleur de vue. La clé pour que cela fonctionne est ici, votre contrôleur de vue doit adopter la UIWebViewDelegate
et dans le func webViewDidFinishLoad(_ webView: UIWebView)
vous pouvez voir que le 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)")
}
}
}
En utilisant des éléments de fragilecat pour y répondre, j'ai créé un exemple de projet avec trois contrôleurs de vue:
Mes nombreuses heures perdues sur ce problème me disent que UIMarkupTextPrintFormatter
ne prend en charge les images. Deux raisons à cela:
UIPrintInteractionController
avec une UIMarkupTextPrintFormatter
montre les images correctement.UIMarkupTextPrintFormatter
. J'ai enquêté et trouvé que la raison en était que le code HTML avait été chargé auparavant dans une UIWebView
. Il semble que UIMarkupTextPrintFormatter
s'appuie sur un composant WebKit
pour restituer ses images.Je suis conscient que je ne fournis aucune solution mais pour moi, il s'agit clairement d'un bogue iOS. Je n'ai pas iOS 11, alors peut-être que cela a été résolu dans cette version à venir.
J'ai décrit le bogue en détail ici avec un exemple d'application permettant de créer des PDF à l'aide des différents formateurs d'impression disponibles.
NB: Je n'ai réussi à obtenir que des images Base64 et "externes" (c'est-à-dire http://example.com/my-image.png ).
Remplacer
let printFormatter = UIMarkupTextPrintFormatter(markupText: htmlContent)
printPageRenderer.addPrintFormatter(printFormatter, startingAtPageAt: 0)
avec
let printFormatter = wkWebView.viewPrintFormatter()
printPageRenderer.addPrintFormatter(printFormatter, startingAtPageAt: 0)
où 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
.
Comme beaucoup de gens ici, j'ai perdu des heures à résoudre ce problème. J'ai utilisé des images en base 64, pas des images externes. Ce qui suit fonctionne bien pour moi avec les images chargées dans la balise html <img src="data:image/jpeg;base64,..." />
et également avec css en utilisant background-image: "url('data:image/jpeg;base64,...)"
.
Voici ce que j'ai fini avec:
UIMarkupTextPrintFormatter
mais webPreview.viewPrintFormatter()
à la place, je ne sais pas pourquoi mais cela fonctionne comme ça.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/
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 PDF le code de conversion 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);
}
}
}