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

时间:2022-10-30 21:22:19

I generate a PDF in my Swift application from some HTML. I use a UIMarkupTextPrintFormatter and have code similar to this gist. I get the PDF as NSData and attach it to an email. The app does not show the PDF to the user before attaching it.

我从一些HTML在Swift应用程序中生成PDF。我使用UIMarkupTextPrintFormatter并具有类似于这个要点的代码。我将PDF作为NSData并将其附加到电子邮件中。在附加PDF之前,应用程序不会向用户显示PDF。

I'd now like to include some images. Adding their NSURL in HTML with my current PDF generating strategy doesn't work. How can I get NSData of a PDF corresponding to my HTML with images added? Here are some things I've tried:

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

  1. This answer suggests embedding the base64 image in the HTML and using UIPrintInteractionController. This does give me a print preview with correctly-embedded images but how do I go from there to NSData corresponding to the PDF output?

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

  2. I've seen some similar suggestions going through UIWebView but those lead to the same issue -- I don't want to show a preview to the user.

    我已经看到一些类似的建议通过UIWebView,但那些导致相同的问题 - 我不想向用户显示预览。

4 个解决方案

#1


22  

The UIMarkupTextPrintFormatter does not seem to support the html img tag. Apple's documentation is not very informative here, it simply states that the initialization parameter is "The HTML markup text for the print formatter". There is no indication of exactly what tags are supported by the print formatter.

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

After many tests the only conclusion I can draw is that UIMarkupTextPrintFormatter does NOT support displaying images.

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

So where does that leave people who want the convenience of creating PDF's from HTML content?

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

So the only way I have found to make this work is to use a hidden web view that you load your html content in and then use the web view's UIViewPrintFormatter. This works but really feels like a hack.

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

It does work and it will embed images in your PDF document, however if it was me I would lean towards using CoreText and Quartz 2D as you would have much more control of the pdf generation process, having said that I understand it might be overkill, I don't know the size or complexity of your html content.

它确实有效,它会在你的PDF文档中嵌入图像,但如果是我,我会倾向于使用CoreText和Quartz 2D,因为你可以更好地控制pdf生成过程,我说我明白它可能有点过分,我不知道你的HTML内容的大小或复杂性。

So on to a working example...

那么就是一个工作的例子......

Setup

建立

It was useful to define a base url so that I could just pass in the filenames of the images I wanted to use. The base url mapped to a directory in the app bundle where the images are located. You can define your own location too.

定义基本URL非常有用,这样我就可以传入我想要使用的图像的文件名。基本URL映射到应用程序包中图像所在的目录。您也可以定义自己的位置。

Bundle.main.resourceURL + "www/"

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

Then I created a protocol to handle document related functionality. Default implementations are provide by an extension as you can see in the code below.

然后我创建了一个协议来处理与文档相关的功能。默认实现由扩展提供,如下面的代码所示。

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
    }

}

Then I had this protocol adopted by a view controller. The key to making this work is here, your view controller needs to adopt the UIWebViewDelegate and in the func webViewDidFinishLoad(_ webView: UIWebView) you can see the pdf is created.

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


    }


}

#2


5  

Using parts from fragilecat his answer, I've put together a sample project with three viewcontrollers:

使用来自fragilecat的部分他的答案,我已经整理了一个包含三个viewcontrollers的示例项目:

  • The first one renders local HTML with one image using WKWebView, then exports to PDF
  • 第一个使用WKWebView使用一个图像呈现本地HTML,然后导出为PDF
  • The second one renders the PDF with another WKWebView
  • 第二个用另一个WKWebView呈现PDF
  • The third one shows the print dialog
  • 第三个显示打印对话框

https://github.com/bvankuik/TestMakeAndPrintPDF

https://github.com/bvankuik/TestMakeAndPrintPDF

#3


5  

My many hours wasted on this issue tells me UIMarkupTextPrintFormatter does support images. Two reasons for this:

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

  1. As you say, the PDF shown by UIPrintInteractionController with a UIMarkupTextPrintFormatter shows the images correctly.
  2. 正如您所说,UIPrintInteractionController与UIMarkupTextPrintFormatter显示的PDF正确显示图像。
  3. The following tutorial (also mentioned in the comments) actually manages to create a PDF with images using UIMarkupTextPrintFormatter. I've investigated and found the reason for this was that the HTML code was loaded beforehand in a UIWebView. It looks like UIMarkupTextPrintFormatter relies on some WebKit component to render its images.
  4. 以下教程(在评论中也提到)实际上设法使用UIMarkupTextPrintFormatter创建带有图像的PDF。我已经调查过,发现原因是HTML代码是事先在UIWebView中加载的。看起来UIMarkupTextPrintFormatter依赖于某些WebKit组件来渲染其图像。

I'm aware I'm not providing any solution but to me this is clearly an iOS bug. I don't have iOS 11 so maybe it has been solved in this upcoming version.

我知道我没有提供任何解决方案,但对我来说这显然是一个iOS错误。我没有iOS 11,所以可能已经在即将推出的版本中解决了。

I've described the bug in detail here along with a sample app that allows creation of PDFs using the different print formatters available.

我在这里详细描述了这个bug以及一个允许使用不同的打印格式化程序创建PDF的示例应用程序。

NB: I've only managed to get Base64 and "external" (i.e. http://example.com/my-image.png) images working.

注意:我只是设法让Base64和“外部”(即http://example.com/my-image.png)图像正常工作。

#4


0  

Replace

更换

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

with

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

where wkWebView is your instance of WKWebView in which you have previously loaded the HTML content htmlContent that contains an image, and printPageRenderer is your instance of UIPrintPageRenderer.

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

#1


22  

The UIMarkupTextPrintFormatter does not seem to support the html img tag. Apple's documentation is not very informative here, it simply states that the initialization parameter is "The HTML markup text for the print formatter". There is no indication of exactly what tags are supported by the print formatter.

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

After many tests the only conclusion I can draw is that UIMarkupTextPrintFormatter does NOT support displaying images.

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

So where does that leave people who want the convenience of creating PDF's from HTML content?

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

So the only way I have found to make this work is to use a hidden web view that you load your html content in and then use the web view's UIViewPrintFormatter. This works but really feels like a hack.

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

It does work and it will embed images in your PDF document, however if it was me I would lean towards using CoreText and Quartz 2D as you would have much more control of the pdf generation process, having said that I understand it might be overkill, I don't know the size or complexity of your html content.

它确实有效,它会在你的PDF文档中嵌入图像,但如果是我,我会倾向于使用CoreText和Quartz 2D,因为你可以更好地控制pdf生成过程,我说我明白它可能有点过分,我不知道你的HTML内容的大小或复杂性。

So on to a working example...

那么就是一个工作的例子......

Setup

建立

It was useful to define a base url so that I could just pass in the filenames of the images I wanted to use. The base url mapped to a directory in the app bundle where the images are located. You can define your own location too.

定义基本URL非常有用,这样我就可以传入我想要使用的图像的文件名。基本URL映射到应用程序包中图像所在的目录。您也可以定义自己的位置。

Bundle.main.resourceURL + "www/"

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

Then I created a protocol to handle document related functionality. Default implementations are provide by an extension as you can see in the code below.

然后我创建了一个协议来处理与文档相关的功能。默认实现由扩展提供,如下面的代码所示。

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
    }

}

Then I had this protocol adopted by a view controller. The key to making this work is here, your view controller needs to adopt the UIWebViewDelegate and in the func webViewDidFinishLoad(_ webView: UIWebView) you can see the pdf is created.

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


    }


}

#2


5  

Using parts from fragilecat his answer, I've put together a sample project with three viewcontrollers:

使用来自fragilecat的部分他的答案,我已经整理了一个包含三个viewcontrollers的示例项目:

  • The first one renders local HTML with one image using WKWebView, then exports to PDF
  • 第一个使用WKWebView使用一个图像呈现本地HTML,然后导出为PDF
  • The second one renders the PDF with another WKWebView
  • 第二个用另一个WKWebView呈现PDF
  • The third one shows the print dialog
  • 第三个显示打印对话框

https://github.com/bvankuik/TestMakeAndPrintPDF

https://github.com/bvankuik/TestMakeAndPrintPDF

#3


5  

My many hours wasted on this issue tells me UIMarkupTextPrintFormatter does support images. Two reasons for this:

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

  1. As you say, the PDF shown by UIPrintInteractionController with a UIMarkupTextPrintFormatter shows the images correctly.
  2. 正如您所说,UIPrintInteractionController与UIMarkupTextPrintFormatter显示的PDF正确显示图像。
  3. The following tutorial (also mentioned in the comments) actually manages to create a PDF with images using UIMarkupTextPrintFormatter. I've investigated and found the reason for this was that the HTML code was loaded beforehand in a UIWebView. It looks like UIMarkupTextPrintFormatter relies on some WebKit component to render its images.
  4. 以下教程(在评论中也提到)实际上设法使用UIMarkupTextPrintFormatter创建带有图像的PDF。我已经调查过,发现原因是HTML代码是事先在UIWebView中加载的。看起来UIMarkupTextPrintFormatter依赖于某些WebKit组件来渲染其图像。

I'm aware I'm not providing any solution but to me this is clearly an iOS bug. I don't have iOS 11 so maybe it has been solved in this upcoming version.

我知道我没有提供任何解决方案,但对我来说这显然是一个iOS错误。我没有iOS 11,所以可能已经在即将推出的版本中解决了。

I've described the bug in detail here along with a sample app that allows creation of PDFs using the different print formatters available.

我在这里详细描述了这个bug以及一个允许使用不同的打印格式化程序创建PDF的示例应用程序。

NB: I've only managed to get Base64 and "external" (i.e. http://example.com/my-image.png) images working.

注意:我只是设法让Base64和“外部”(即http://example.com/my-image.png)图像正常工作。

#4


0  

Replace

更换

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

with

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

where wkWebView is your instance of WKWebView in which you have previously loaded the HTML content htmlContent that contains an image, and printPageRenderer is your instance of UIPrintPageRenderer.

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