Подтвердить что ты не робот

Создание PDF файлов с изображениями из HTML в Swift без отображения интерфейса печати

Я создаю PDF файл в приложении Swift из некоторого HTML. Я использую UIMarkupTextPrintFormatter и имею код, похожий на этот смысл. Я получаю PDF как NSData и прикрепляю его к электронному письму. Приложение не показывает PDF пользователю, прежде чем присоединить его.

Теперь я хотел бы включить некоторые изображения. Добавление их NSURL в HTML с моей текущей стратегией генерации PDF не работает. Как я могу получить NSData PDF, соответствующий моему HTML, с добавленными изображениями? Вот несколько вещей, которые я пробовал:

  • Этот ответ предлагает внедрить образ base64 в HTML и использовать UIPrintInteractionController. Это дает мне предварительный просмотр печати с правильно встроенными изображениями, но как мне перейти оттуда к NSData, соответствующему выходу PDF?

  • Я видел некоторые подобные предложения, проходящие через UIWebView, но это приводит к одной и той же проблеме - я не хочу показывать пользователю предварительный просмотр.

4b9b3361

Ответ 1

UIMarkupTextPrintFormatter, похоже, не поддерживает тег html img. Документация Apple здесь не очень информативна, она просто утверждает, что параметром инициализации является "Текст разметки HTML для форматирования печати". Нет никаких указаний на то, какие теги поддерживаются форматом печати.

После многих тестов единственный вывод, который я могу сделать, заключается в том, что UIMarkupTextPrintFormatter поддерживает НЕ отображение изображений.

Итак, где же это оставляет людей, которые хотят, чтобы удобство создания PDF из содержимого HTML?

Таким образом, единственный способ, которым я нашел эту работу, - использовать скрытый веб-вид, в который вы загружаете свой html-контент, а затем использовать веб-представление 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
    }

}

Тогда у меня был этот протокол, принятый контроллером вида. Ключ к выполнению этой работы здесь, ваш контроллер представления должен принять 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

Используя части от fragilecat его ответа, я собрал образец проекта с тремя viewcontrollers:

  • Первый создает локальный HTML с одним изображением с помощью WKWebView, затем экспорт в PDF
  • Вторая делает PDF с другим WKWebView
  • Третий показывает диалог печати

https://github.com/bvankuik/TestMakeAndPrintPDF

Ответ 3

Мои многочисленные часы, потраченные впустую на эту проблему, говорят мне, что UIMarkupTextPrintFormatter поддерживает. Две причины для этого:

  • Как вы говорите, PDF, показанный UIPrintInteractionController с UIMarkupTextPrintFormatter, показывает изображения правильно.
  • Следующий учебник (также упоминаемый в комментариях) действительно удается создать PDF файл с изображениями с помощью UIMarkupTextPrintFormatter. Я исследовал и нашел причину этого в том, что код HTML был загружен заранее в UIWebView. Похоже, что UIMarkupTextPrintFormatter полагается на некоторый компонент WebKit для рендеринга его изображений.

Я знаю, что я не предлагаю никаких решений, но для меня это явно ошибка iOS. У меня нет iOS 11, поэтому, возможно, он был решен в этой предстоящей версии.

Я подробно описал ошибку здесь и примерное приложение, которое позволяет создавать PDF файлы с использованием различных форматов печати. ​​

NB: мне удалось получить только изображения Base64 и "внешние" (т.е. http://example.com/my-image.png).