HTMLからSwift
アプリケーションでPDFを生成します。 UIMarkupTextPrintFormatter
を使用し、 this Gist に似たコードを持っています。 PDFをNSData
として取得し、メールに添付します。アプリは、アタッチする前にユーザーにPDFを表示しません。
画像を含めたいと思います。現在のPDF生成戦略にHTMLでNSURL
を追加しても機能しません。画像が追加されたHTMLに対応するPDFのNSData
を取得するにはどうすればよいですか?ここに私が試したものがいくつかあります:
この回答 は、HTMLにbase64画像を埋め込み、 UIPrintInteractionController
を使用することを提案しています。これにより、画像が正しく埋め込まれた印刷プレビューが表示されますが、そこからPDF出力に対応するNSData
に移動するにはどうすればよいですか?
UIWebView
を通過するいくつかの同様の提案を見てきましたが、それらは同じ問題につながります。ユーザーにプレビューを表示したくありません。
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/"
_
次に、ドキュメント関連の機能を処理するプロトコルを作成しました。以下のコードでわかるように、デフォルトの実装は拡張機能によって提供されます。
_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)")
}
}
}
_
彼の答えのfragilecatのパーツを使用して、Swift 5:
この問題に費やされた私の多くの時間は、UIMarkupTextPrintFormatter
doesサポート画像を教えてくれます。これには2つの理由があります。
UIPrintInteractionController
とUIMarkupTextPrintFormatter
で示され、画像を正しく表示します。UIMarkupTextPrintFormatter
を使用してPDFを使用してイメージを作成します。これは、HTMLコードが事前にUIWebView
にロードされていたためで、UIMarkupTextPrintFormatter
はその画像をレンダリングするためにWebKit
コンポーネントに依存しているようです。ソリューションを提供していないことは承知していますが、これは明らかにiOSのバグです。私はiOS 11を持っていないので、多分それはこの次のバージョンで解決されました。
バグを詳細に説明しました here 利用可能なさまざまな印刷フォーマッタを使用してPDFを作成できるサンプルアプリと共に。
NB:Base64と "external"(つまり http://example.com/my-image.png )の画像のみが機能するようになりました。
交換
let printFormatter = UIMarkupTextPrintFormatter(markupText: htmlContent)
printPageRenderer.addPrintFormatter(printFormatter, startingAtPageAt: 0)
と
let printFormatter = wkWebView.viewPrintFormatter()
printPageRenderer.addPrintFormatter(printFormatter, startingAtPageAt: 0)
wkWebView
は、画像を含むHTMLコンテンツWKWebView
を以前に読み込んだhtmlContent
のインスタンスであり、printPageRenderer
はUIPrintPageRenderer
のインスタンスです。
まず、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でうまく機能します。
ここに私が終わったものがあります:
UIMarkupTextPrintFormatter
を使用せず、代わりにwebPreview.viewPrintFormatter()
を使用します。理由はわかりませんが、そのように動作します。_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/
_