簡體   English   中英

在 iOS 上將 SwiftUI 視圖轉換為 PDF

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM