在Swift中使用HTML中的图像生成PDF而不显示打印界面

时间:2016-11-01 20:30:26

标签: ios swift pdf

我在某个HTML的Swift应用程序中生成PDF。我使用UIMarkupTextPrintFormatter并且代码类似于this gist。我将PDF格式化为NSData并将其附加到电子邮件中。在附加PDF之前,应用程序不会向用户显示PDF。

我现在想要包含一些图片。使用我当前的PDF生成策略在HTML中添加NSURL不起作用。如何在添加图像的情况下获取与我的HTML相对应的NSData PDF?以下是我尝试过的一些事情:

  1. This answer建议在HTML中嵌入base64图像并使用UIPrintInteractionController。这确实为我提供了正确嵌入图像的打印预览,但是如何从那里转到与PDF输出相对应的NSData

  2. 我在UIWebView看到了一些类似的建议,但这些建议导致同样的问题 - 我不想向用户展示预览。

6 个答案:

答案 0 :(得分:26)

UIMarkupTextPrintFormatter似乎不支持html img标记。 Apple的文档在这里没有很多信息,它只是声明初始化参数是“打印格式化程序的HTML标记文本”。没有迹象表明打印格式化程序支持哪些标记。

经过多次测试后,我可以得出的唯一结论是UIMarkupTextPrintFormatter 支持显示图像。

那么,那些希望从HTML内容创建PDF的便利性的人会在哪里?

因此,我发现使用此工作的唯一方法是使用隐藏的Web视图,您可以在其中加载html内容,然后使用Web视图的UIViewPrintFormatter。这可行,但真的感觉像一个黑客。

它确实有效,它会将图像嵌入到PDF文档中,但如果是我,我会倾向于使用CoreTextQuartz 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
    }

}

然后我让视图控制器采用了这个协议。完成这项工作的关键在于,您的视图控制器需要采用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)")
        }


    }


}

答案 1 :(得分:7)

使用来自fragilecat的部分他的回答,我在Swift 5中整理了一个带有三个viewcontrollers的示例项目:

  • 第一个使用WKWebView使用一个图像呈现本地HTML,然后 出口到PDF
  • 第二个用另一个WKWebView
  • 呈现PDF
  • 第三个显示打印对话框

https://github.com/bvankuik/TestMakeAndPrintPDF

答案 2 :(得分:7)

我在此问题上浪费了很多时间告诉我UIMarkupTextPrintFormatter 支持支持图片。有两个原因:

  1. 正如您所说,UIPrintInteractionController显示的带UIMarkupTextPrintFormatter的PDF正确显示图像。
  2. 以下tutorial(也在评论中提到)实际上设法使用UIMarkupTextPrintFormatter创建包含图像的PDF。我调查过,发现原因是HTML代码预先加载到UIWebView。看起来UIMarkupTextPrintFormatter依赖于某些WebKit组件来呈现其图像。
  3. 我知道我没有提供任何解决方案,但对我来说这显然是一个iOS错误。我没有iOS 11所以可能已经在即将推出的版本中解决了。

    我已经详细描述了错误here以及一个示例应用程序,该应用程序允许使用不同的打印格式化程序创建PDF。

    注意:我只能设法获得Base64和&#34;外部&#34; (即http://example.com/my-image.png)图像正常工作。

答案 3 :(得分:4)

替换

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

使用

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

其中wkWebView是您的WKWebView的实例,您之前在其中加载了包含图像的HTML内容htmlContent,而printPageRenderer是您的{{1 }}。

答案 4 :(得分:1)

首先,创建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);
       }
     }
}

答案 5 :(得分:0)

就像这里的许多人一样,我浪费了很多时间解决这个问题。我使用的是base-64映像,而不是外部映像。以下内容对于在<img src="data:image/jpeg;base64,..." /> html标记中加载的图像以及使用background-image: "url('data:image/jpeg;base64,...)"的css而言都非常适合我。

这就是我最终得到的:

  • 原始问题要求在生成PDF之前不必显示Web视图。我并不需要它,但是如果您按照@Pete Hornsby (accepted answer)的建议将其隐藏起来即可。
  • As @fivewood mentioned不使用UIMarkupTextPrintFormatter,而是使用webPreview.viewPrintFormatter(),我不知道为什么,但是它像那样工作。
  • 必需:在生成PDF(pointed here)之前,等待Web视图加载。
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/