[英]Convert SwiftUI View to PDF on iOS
我正在用 SwiftUI 绘制一些漂亮的图形,因为它非常简单易行。 然后我想将整个 SwiftUI 视图导出为 PDF,以便其他人可以以一种很好的方式查看图形。 SwiftUI 没有直接为此提供解决方案。
干杯,
亚历克斯
经过一番思考,我想到了将 UIKit 转 PDF 方法和 SwiftUI 结合起来的想法。
首先创建 SwiftUI 视图,然后放入 UIHostingController。 您在所有其他视图后面的窗口上渲染 HostingController 并在 PDF 上绘制其图层。 下面列出了示例代码。
func exportToPDF() {
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let outputFileURL = documentDirectory.appendingPathComponent("SwiftUI.pdf")
//Normal with
let width: CGFloat = 8.5 * 72.0
//Estimate the height of your view
let height: CGFloat = 1000
let charts = ChartsView()
let pdfVC = UIHostingController(rootView: charts)
pdfVC.view.frame = CGRect(x: 0, y: 0, width: width, height: height)
//Render the view behind all other views
let rootVC = UIApplication.shared.windows.first?.rootViewController
rootVC?.addChild(pdfVC)
rootVC?.view.insertSubview(pdfVC.view, at: 0)
//Render the PDF
let pdfRenderer = UIGraphicsPDFRenderer(bounds: CGRect(x: 0, y: 0, width: 8.5 * 72.0, height: height))
do {
try pdfRenderer.writePDF(to: outputFileURL, withActions: { (context) in
context.beginPage()
pdfVC.view.layer.render(in: context.cgContext)
})
self.exportURL = outputFileURL
self.showExportSheet = true
}catch {
self.showError = true
print("Could not create PDF file: \(error)")
}
pdfVC.removeFromParent()
pdfVC.view.removeFromSuperview()
}
我喜欢这个答案,但无法让它发挥作用。 我遇到了一个异常并且没有执行捕获。
经过一番摸索,并写了一个 SO Question 询问如何调试它(我从未提交过),我意识到解决方案虽然不明显,但很简单:将渲染阶段包装在对主队列的异步调用中:
//Render the PDF
let pdfRenderer = UIGraphicsPDFRenderer(bounds: CGRect(x: 0, y: 0, width: 8.5 * 72.0, height: height))
DispatchQueue.main.async {
do {
try pdfRenderer.writePDF(to: outputFileURL, withActions: { (context) in
context.beginPage()
pdfVC.view.layer.render(in: context.cgContext)
})
print("wrote file to: \(outputFileURL.path)")
} catch {
print("Could not create PDF file: \(error.localizedDescription)")
}
}
谢谢,SnOwfreeze!
我尝试了所有答案,但它们对我不起作用(Xcode 12.4、iOS 14.4)。 这是对我有用的:
import SwiftUI
func exportToPDF() {
let outputFileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("SwiftUI.pdf")
let pageSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
//View to render on PDF
let myUIHostingController = UIHostingController(rootView: ContentView())
myUIHostingController.view.frame = CGRect(origin: .zero, size: pageSize)
//Render the view behind all other views
guard let rootVC = UIApplication.shared.windows.first?.rootViewController else {
print("ERROR: Could not find root ViewController.")
return
}
rootVC.addChild(myUIHostingController)
//at: 0 -> draws behind all other views
//at: UIApplication.shared.windows.count -> draw in front
rootVC.view.insertSubview(myUIHostingController.view, at: 0)
//Render the PDF
let pdfRenderer = UIGraphicsPDFRenderer(bounds: CGRect(origin: .zero, size: pageSize))
DispatchQueue.main.async {
do {
try pdfRenderer.writePDF(to: outputFileURL, withActions: { (context) in
context.beginPage()
myUIHostingController.view.layer.render(in: context.cgContext)
})
print("wrote file to: \(outputFileURL.path)")
} catch {
print("Could not create PDF file: \(error.localizedDescription)")
}
//Remove rendered view
myUIHostingController.removeFromParent()
myUIHostingController.view.removeFromSuperview()
}
}
注意:当您尝试导出相同的视图时,不要尝试在带有 .onAppear 的视图中使用此功能。 这自然会导致无限循环。
当我尝试使用其他答案中的解决方案生成 PDF 文件时,我只得到了一个模糊的 PDF 并且质量很差。
我最终在一个更大的框架中生成了 SwiftUI 视图,并将上下文缩小到适当的大小。
这是我对Sn0wfreeze 的回答的修改:
// scale 1 -> 72 DPI
// scale 4 -> 288 DPI
// scale 300 / 72 -> 300 DPI
let dpiScale: CGFloat = 4
// for US letter page size
let pageSize = CGSize(width: 8.5 * 72, height: 11 * 72)
// for A4 page size
// let pageSize = CGSize(width: 8.27 * 72, height: 11.69 * 72)
let pdfVC = UIHostingController(rootView: swiftUIview)
pdfVC.view.frame = CGRect(origin: .zero, size: pageSize * dpiScale)
...
let pdfRenderer = UIGraphicsPDFRenderer(bounds: CGRect(origin: .zero, size: pageSize))
do {
try pdfRenderer.writePDF(to: outputFileURL) { context in
context.beginPage()
context.cgContext.scaleBy(x: 1 / dpiScale, y: 1 / dpiScale)
pdfVC.view.layer.render(in: context.cgContext)
}
print("File saved to: \(outputFileURL.path)")
}
...
我还使用以下扩展将 CGSize 乘以 CGFloat:
extension CGSize {
static func * (size: CGSize, value: CGFloat) -> CGSize {
return CGSize(width: size.width * value, height: size.height * value)
}
}
我尝试了上面的其他方法,但仍然无法正确显示视图,所以这是我在 Xcode 12.2 和 Swift 5 上对我有用的方法,在弄乱了Sn0wfreeze 的答案和pawello2222 的答案之后:
func exportToPDF() {
let outputFileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("SwiftUI.pdf")
let pageSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
let rootVC = UIApplication.shared.windows.first?.rootViewController
//Render the PDF
let pdfRenderer = UIGraphicsPDFRenderer(bounds: CGRect(origin: .zero, size: pageSize))
DispatchQueue.main.async {
do {
try pdfRenderer.writePDF(to: outputFileURL, withActions: { (context) in
context.beginPage()
rootVC?.view.layer.render(in: context.cgContext)
})
print("wrote file to: \(outputFileURL.path)")
} catch {
print("Could not create PDF file: \(error.localizedDescription)")
}
}
}
基本上,我删除了对新创建的“pdfVC”视图控制器的任何引用,并且始终将主视图引用为我们的“rootVC”。 我抓取屏幕宽度和高度并使用它来定义我们的 PDF 的宽度和高度,而不是尝试将视图缩放到适当的 A4 或字母大小。 这将创建整个显示 SwiftUI 视图的 PDF。
希望这可以帮助任何对其他答案有任何问题的人! :)
func exportToPDF() {
let dpiScale: CGFloat = 1.5
// for US letter page size
// let pageSize = CGSize(width: 8.5 * 72, height: 11 * 72)
// for A4 page size
let pageSize = CGSize(width: 8.27 * 72, height: 11.69 * 72)
//View to render on PDF
let myUIHostingController = UIHostingController(rootView: self)// when we render the current view
myUIHostingController.view.frame = CGRect(origin: .zero, size: pageSize * dpiScale)
myUIHostingController.view.overrideUserInterfaceStyle = .light//it will be light, when dark mode
//Render the view behind all other views
guard let rootVC = UIApplication.shared.windows.first?.rootViewController else {
print("ERROR: Could not find root ViewController.")
return
}
rootVC.addChild(myUIHostingController)
//at: 0 -> draws behind all other views
//at: UIApplication.shared.windows.count -> draw in front
rootVC.view.insertSubview(myUIHostingController.view, at: 0)
//Render the PDF
let pdfRenderer = UIGraphicsPDFRenderer(bounds: CGRect(origin: .zero, size: pageSize))
DispatchQueue.main.async {
do {
try pdfRenderer.writePDF(to: outputFileURL) { context in
context.beginPage()
context.cgContext.scaleBy(x: 1 / dpiScale, y: 1 / dpiScale)
myUIHostingController.view.layer.render(in: context.cgContext)
}
print("wrote file to: \(outputFileURL.path)")
} catch {
print("Could not create PDF file: \(error.localizedDescription)")
}
//Remove rendered view
myUIHostingController.removeFromParent()
myUIHostingController.view.removeFromSuperview()
}
}
当我尝试渲染当前视图时,我收到了警告。 我修好了它。 我添加了一行,以便在设备中打开暗模式时 pdf 文档保持亮起。 这对我来说是最好的解决方案,我也希望你。 谢谢pawello2222
这是 pawello2222 所需的扩展名
extension CGSize {
static func * (size: CGSize, value: CGFloat) -> CGSize {
return CGSize(width: size.width * value, height: size.height * value)
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.