swift - UIMarkupTextPrintFormatter 和 Mac Catalyst
问题描述
我有一个使用 Catalyst 成功迁移到 Mac 的 iPad 应用程序。
虽然我可以在 iPad/iPhone 上使用 生成 PDF UIMarkupTextPrintFormatter
,但它在 Mac 上无法正常工作。
事实上,我什至无法构建 Mac 二进制文件,除非我注释掉UIMarkupTextPrintFormatter
using #if !targetEnvironment(macCatalyst)
,因为 Xcode 只会出现错误:
体系结构 x86_64 的未定义符号:
“_OBJC_CLASS_$_UIMarkupTextPrintFormatter”,引用自:Functions.o ld 中的 objc-class-ref:未找到体系结构 x86_64 的符号:错误:链接器命令失败,退出代码为 1(使用 -v查看调用)
这令人困惑,因为 Apple 的文档表明它与 Mac Catalyst 13.0+ 兼容 https://developer.apple.com/documentation/uikit/uimarkuptextprintformatter
有没有其他人经历过这种情况,你能找到解决方案吗?
谢谢你。
编辑:根据 Sam Wize 在此处的帖子,我找到了一个出色的解决方案,该解决方案无需修改即可在 macCatalyst 中使用:
https://samwize.com/2019/07/02/how-to-generate-pdf-with-images/
关键是使用 WKWebView 对象(但不显示它)作为中介来加载 HTML 文件,然后使用它的 viewPrintFormatter 通过其didFinish navigation:
委托呈现 PDF
这是我的代码(希望评论是不言自明的)。使用以下代码创建一个名为 PDFCreator.swift 的 Swift 文件:
import WebKit
typealias PDFCompletion = (Result<NSData, Error>) -> Void
class PDFCreator: NSObject {
var webView: WKWebView? = nil
var completion: PDFCompletion!
func exportPDF(html: String, completion: @escaping PDFCompletion) throws {
// Set up the completion handler to be called by the function in the delegate method
// It has to be instantiated here so the delegate method can access it
self.completion = completion
// Creates a WebKit webView to load the HTML string & sets the delegate (self) to respond
let webView = WKWebView()
webView.navigationDelegate = self
// If the other assets are in the same baseURL location (eg. Temporary Documents Directory, they will also render)
// But you need to ensure the assets are already there before calling this function
let baseURL = URL(fileURLWithPath: NSTemporaryDirectory())
// Loads the HTML string into the WebView and renders it (invisibly) with any assets
webView.loadHTMLString(html, baseURL: baseURL)
self.webView = webView
// After this function closes, the didFinish navigation delegate method is called
}
func createPDF(_ formatter: UIViewPrintFormatter) {
// Subclass UIPrintPageRenderer if you want to add headers/footers, page counts etc.
let printPageRenderer = UIPrintPageRenderer()
printPageRenderer.addPrintFormatter(formatter, startingAtPageAt: 0)
// Assign paperRect and printableRect
// A4, 72 dpi
let paperRect = CGRect(x: 0, y: 0, width: 595.2, height: 841.8)
let padding: CGFloat = 20
let printableRect = paperRect.insetBy(dx: padding, dy: padding)
printPageRenderer.setValue(printableRect, forKey: "printableRect")
printPageRenderer.setValue(paperRect, forKey: "paperRect")
// Assign header & footer dimensions
printPageRenderer.footerHeight = 70
printPageRenderer.headerHeight = 20
// Create PDF context and draw
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, .zero, nil)
for i in 0..<printPageRenderer.numberOfPages {
UIGraphicsBeginPDFPage();
printPageRenderer.drawPage(at: i, in: UIGraphicsGetPDFContextBounds())
}
UIGraphicsEndPDFContext();
// Send the PDF data out with a Result of 'success' & the NSData object for processing in the completion block
self.completion?(.success(pdfData))
}
}
extension PDFCreator: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let viewPrintFormatter = webView.viewPrintFormatter()
createPDF(viewPrintFormatter)
}
}
在我的应用程序中,我实例化了一个 PDFCreator 对象
let pdfCreator = PDFCreator()
然后我确保首先在相同的“baseURL”位置创建 HTML 文件所需的所有本地资产 - 在我的情况下NSTemporaryDirectory()
- 然后运行以下命令:
let pdfFilePath = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("test.pdf")
try? pdfCreator.exportPDF(html: htmlString, completion: { (result) in
switch result {
case .success(let data):
try? data.write(to: pdfFilePath, options: .atomic)
// *** Do stuff with the file at pdfFilePath ***
case .failure(let error):
print(error.localizedDescription)
}
})
解决方案
我也有同样的问题。但是我能够通过使用 Swift 的函数将 html 转换为属性文本,然后使用 UISimpleTextPrintFormatter 和属性文本来解决它。
我的原始代码:
let formatter = UIMarkupTextPrintFormatter(markupText: htmlString)
formatter.perPageContentInsets = UIEdgeInsets(top: 70.0, left: 60.0, bottom: 70.0, right: 60.0)
printController.printFormatter = formatter
printController.present(animated: true, completionHandler: nil)
在 Catalyst(和 iOS)上工作:
guard let printData = htmlString.data(using: String.Encoding.utf8) else { return }
do {
let printText = try NSAttributedString(data: printData, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
let formatter = UISimpleTextPrintFormatter(attributedText: printText)
formatter.perPageContentInsets = UIEdgeInsets(top: 70.0, left: 60.0, bottom: 70.0, right: 60.0)
printController.printFormatter = formatter
printController.present(animated: true, completionHandler: nil)
} catch {
print(error)
}
但是, NSAttributedString(data: ) 似乎比在 iOS 上对 Catalyst 上的内容更敏感。例如,我是否对在 iOS 上运行良好的表格有问题。所以这不是一个完美的解决方案。
编辑 似乎可以更好地处理例如表格的更好解决方案是:
func compHandler(attributedString:NSAttributedString?, attributeKey:[NSAttributedString.DocumentAttributeKey : Any]?, error:Error?) -> Void {
guard let printText = attributedString else { return }
let formatter = UISimpleTextPrintFormatter(attributedText: printText)
formatter.perPageContentInsets = UIEdgeInsets(top: 70.0, left: 60.0, bottom: 70.0, right: 60.0)
printController.printFormatter = formatter
printController.present(animated: true, completionHandler: nil)
}
guard let printData = htmlString.data(using: String.Encoding.utf8) else { return }
NSAttributedString.loadFromHTML(data: printData, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], completionHandler: compHandler)
推荐阅读
- python - 为来自 url_for 的所有结果添加前缀,而不是路由所需的前缀
- postman - 如何从邮递员中的json响应中提取值,其值具有字符串和整数
- scala - 如何在一定范围内翻转Chars的大小写
- r - 在plotly R中更改图例标题“mesh3d”对象
- azure - Azure DevOps 设置多个源以还原包
- ios - 将动画应用于一系列 UILabel 的属性文本
- java - 在 Spring Boot 中使用额外的 JNDI
- split - 使用自动范围分割单元格
- php - PHP数字格式点和逗号
- c# - Asp.netCore 中的重定向与 RedirectToAction