You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用UIGraphicsPDFRenderer在导出PDF中添加可点击链接?

在SwiftUI导出的PDF中添加可点击链接

你的问题在于当前的PDF导出方式是将SwiftUI视图渲染为静态图层,SwiftUI的Link视图只会在App内保留交互性,导出PDF时不会被转换为可点击的PDF链接。要实现这个需求,必须借助PDFKit手动添加PDFAnnotation来创建可点击链接,以下是具体实现步骤:

1. 记录链接视图的位置

首先需要在打印视图中捕获链接文本的坐标,这样才能在PDF中准确定位链接区域:

修改InvoicePrintView,添加绑定属性存储链接的frame,并通过GeometryReader获取视图坐标:

struct InvoicePrintView: View {
    let invoice: Invoice
    @Binding var linkFrame: CGRect // 用于存储链接视图的位置
    
    var body: some View {
        VStack(spacing: 0) {
            VStack {
                if invoice.hasPaymentLink == true {
                    HStack {
                        VStack(alignment: .leading) {
                            Text("Pay your invoice")
                            Text("\(invoice.paymentLinkText ?? "")")
                                .background(
                                    GeometryReader { proxy in
                                        Color.clear
                                            .onAppear {
                                                // 获取视图在打印坐标系内的位置
                                                linkFrame = proxy.frame(in: .named("printView"))
                                            }
                                    }
                                )
                        }
                    }
                }
                // 其他视图内容...
            }
        }
        .coordinateSpace(name: "printView") // 定义专属坐标系,避免全局坐标干扰
    }
}

2. 在主视图中传递绑定

修改InvoiceDetailsView,添加状态变量并传递给InvoicePrintView:

struct InvoiceDetailsView: View {
    @Environment(\.modelContext) var modelContext
    let invoice: Invoice
    @State private var linkFrame: CGRect = .zero // 存储链接位置的状态变量
    
    var body: some View {
        var invoicePrintView = InvoicePrintView(
            invoice: invoice,
            linkFrame: $linkFrame // 传递绑定
        )
        
        // 剩余导航栏和导出按钮代码保持不变...
    }
}

3. 导出PDF后添加链接注释

修改View的exportToPDF扩展,在基础PDF生成后,用PDFKit添加可点击链接:

extension View {
    func exportToPDF(filename: String, invoice: Invoice, 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 {
            completion(nil)
            return
        }
        guard let firstWindow = firstScene.windows.first else {
            completion(nil)
            return
        }
        guard let root = firstWindow.rootViewController else { 
            completion(nil)
            return 
        }
        
        root.addChild(hostingController)
        root.view.insertSubview(hostingController.view, at: 0)
        
        let pdfRenderer = UIGraphicsPDFRenderer(bounds: CGRect(origin: .zero, size: pageSize))
        DispatchQueue.main.async {
            do {
                // 1. 生成基础静态PDF
                try pdfRenderer.writePDF(to: outputFileURL, withActions: { (context) in
                    context.beginPage()
                    hostingController.view.layer.render(in: context.cgContext)
                })
                
                // 2. 如果有支付链接,添加可点击注释
                guard let paymentURL = invoice.paymentLink, // 假设你的Invoice模型有paymentURL属性
                      invoice.hasPaymentLink,
                      !linkFrame.isEmpty else {
                    completion(outputFileURL.path)
                    return
                }
                
                let pdfDocument = PDFDocument(url: outputFileURL)!
                let page = pdfDocument.page(at: 0)! // 假设只有一页
                
                // 转换坐标:PDF坐标系Y轴向上,UIKit坐标系Y轴向下,需要翻转
                let pdfPageBounds = page.bounds(for: .mediaBox)
                let convertedRect = CGRect(
                    x: linkFrame.origin.x,
                    y: pdfPageBounds.height - linkFrame.origin.y - linkFrame.height,
                    width: linkFrame.width,
                    height: linkFrame.height
                )
                
                // 创建链接注释
                let linkAnnotation = PDFAnnotation(
                    bounds: convertedRect,
                    forType: .link,
                    withProperties: nil
                )
                linkAnnotation.url = paymentURL
                page.addAnnotation(linkAnnotation)
                
                // 3. 保存修改后的PDF
                pdfDocument.write(to: outputFileURL)
                
                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()
        }
    }
}

关键说明

  • 为什么Link视图无效:当前导出方式是将SwiftUI视图渲染为位图,只会保留视觉内容,不会将SwiftUI的交互组件转换为PDF的可交互元素。
  • 坐标转换:PDF的坐标系原点在左下角,UIKit在左上角,所以必须翻转Y轴坐标才能准确定位链接区域。
  • 扩展性:如果需要添加多个链接,可以用数组存储多个frame,循环添加注释即可。

内容的提问来源于stack exchange,提问作者Mike Haslam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 21:23:10