Mike Haslam
Mike Haslam

Reputation: 171

How to create a clickable link in exported PDF using UIGraphicsPDFRenderer?

I have a SwiftUI app that successfully exports a PDF and persists the data in SwiftData. I am trying to add a clickable link inside of the exported PDF.

I tried using a link view in exported view but that did not work.

Not sure if PDFAnnotation would be useful or how to use in this case.

Is there a simpler way to do this?

import SwiftUI
import PDFKit
import UIKit

struct InvoiceDetailsView: View {
    @Environment(\.modelContext) var modelContext
   
    let invoice: Invoice
    
    var body: some View {
        // This is the view that gets exported see below
        var invoicePrintView = InvoicePrintView(
            // Other stuff not relevant
            invoice: invoice
        )
        
        NavigationStack {
            VStack(spacing:0){
                ScrollView(.vertical, showsIndicators: false){
                    invoicePrintView
                        .background(
                            Rectangle()
                                .fill(Color.white)
                                .shadow(color: .gray, radius: 2, x: 0, y: 2))
                }
                
            }
            
            .toolbar(content: {
                ToolbarItem(placement: .navigationBarTrailing) {
                    HStack {
                        Button{
                            invoicePrintView.space = 50
                            invoicePrintView.previewSize = 1
                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                                // See extension on view below
                                invoicePrintView.exportToPDF(filename: invoice.invoiceNumber )  { url in
                                    if let urlString = url, let _ = try? Data(contentsOf: URL(fileURLWithPath: urlString)) {
                                        let share = UIActivityViewController(activityItems: [URL(fileURLWithPath: urlString)], applicationActivities: nil)
                                        
                                        guard let firstScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
                                            return
                                        }

                                        guard let firstWindow = firstScene.windows.first else {
                                            return
                                        }
                                        
                                        firstWindow.rootViewController?.present(share, animated: true, completion: nil)
                                    }
                                }
                            }
                        } label: {
                            Image(systemName: "square.and.arrow.up")
                        }
                    }
                }
            })
        }
        
    }
}

struct InvoicePrintView: View {
   
    let invoice: Invoice
    
    var body: some View {
        VStack(spacing:0) {
            VStack {
                //  THIS IS WHERE LINK IS CREATED
                if invoice.hasPaymentLink == true {
                    HStack{
                        VStack(alignment: .leading) {
                            Text("Pay your invoice")
                            Text("\(invoice.paymentLinkText ?? "")")
                        }
                    }
                }
                // Other Stuff in geometrey reader
            }
            
        }
    }
}


// Extension on view

extension View {
    func exportToPDF(filename: String, completion: @escaping (_ url: String?) -> Void) {
        
        
        let outputFileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("Invoice \(filename).pdf")
        let pageSize = CGSize(width: AppConfig.pageWidth*2, height: AppConfig.pageHeight*2)
        let hostingController = UIHostingController(rootView: self)
        hostingController.view.frame = CGRect(origin: .zero, size: pageSize)
        
        guard let firstScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
            return
        }

        guard let firstWindow = firstScene.windows.first else {
            return
        }
        
        guard let root = firstWindow.rootViewController else { return }
        root.addChild(hostingController)
        root.view.insertSubview(hostingController.view, at: 0)
        let pdfRenderer = UIGraphicsPDFRenderer(bounds: CGRect(origin: .zero, size: pageSize))
        DispatchQueue.main.async {
            do {
                try pdfRenderer.writePDF(to: outputFileURL, withActions: { (context) in
                    context.beginPage()
                    hostingController.view.layer.render(in: context.cgContext)
                })
                if AppConfig.showSavedPDFLocation {
                    print("PDF file saved to:\n\(outputFileURL.path)")
                }
                completion(outputFileURL.path)
            } catch {
                print("Could not create PDF file: \(error.localizedDescription)")
                completion(nil)
            }
            hostingController.removeFromParent()
            hostingController.view.removeFromSuperview()
        }
    }
}

Upvotes: 2

Views: 177

Answers (0)

Related Questions