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

SwiftUI中CGContext元素定位及PDF预览问题求助

解决SwiftUI ImageRenderer生成PDF的定位与预览问题

问题1:PDF预览生成失败

你调用CGContext.makeImage()的方式错误——PDF上下文关闭后无法生成图像,且该方法并非用于提取PDF页面内容。正确做法是用ImageRenderer单独渲染预览图,或在PDF渲染流程外独立生成预览内容。

问题2:内容定位在底部(坐标系不匹配)

CGContext的坐标系原点在左下角,而SwiftUI以左上角为原点。需通过坐标变换翻转坐标系:先将原点平移到页面底部,再沿Y轴翻转,让SwiftUI内容正确显示在页面顶部。


修改后的完整代码

import SwiftUI

struct Stat: Identifiable, Equatable {
    var id: String
    let totalMatches: Int
    let points: Int
}

struct GridView: View {
    let stats: [Stat]

    var body: some View {
        Grid(horizontalSpacing: 0, verticalSpacing: 0) {
            GridRow(alignment: .center) {
                Text("ID")
                Text("Matches")
                Text("Points")
            }
            .frame(maxWidth: .infinity, height: 50)
            .bold()
            .font(.headline)
            .background(.pink)

            ForEach(stats) { stat in
                GridRow(alignment: .center) {
                    Text(stat.id)
                    Text(stat.totalMatches, format: .number)
                    Text(stat.points, format: .number)
                }
                .frame(maxWidth: .infinity)
                .background(stat.id == stats.first?.id ? Color.white : Color.gray.opacity(0.2))
            }
        }
        .background(.purple.opacity(0.2))
        .padding() // 给表格添加边距,避免贴边
    }
}

struct ExportImagesView: View {
    @State private var previewImage = Image(systemName: "heart")

    private let stats = [
        Stat(id: UUID().uuidString, totalMatches: 4, points: 12),
        Stat(id: UUID().uuidString, totalMatches: 9, points: 31),
        Stat(id: UUID().uuidString, totalMatches: 4, points: 14),
        Stat(id: UUID().uuidString, totalMatches: 1, points: 0),
    ]

    var body: some View {
        VStack(spacing: 20) {
            previewImage
                .resizable()
                .scaledToFit()
                .frame(height: 300)
            Divider()
            HStack {
                ShareLink("Export PDF", item: renderPDF())
                Spacer()
                Button("Generate & Preview") {
                    _ = renderPDF()
                    updatePreviewImage()
                }
            }
            .padding()
        }
        .padding()
    }

    @MainActor
    func renderPDF() -> URL {
        let url = URL.documentsDirectory.appending(path: "output.pdf")
        let pageSize = CGSize(width: 595.2, height: 841.8) // A4尺寸
        var mediaBox = CGRect(origin: .zero, size: pageSize)

        guard let pdfContext = CGContext(url as CFURL, mediaBox: &mediaBox, nil) else {
            return url
        }

        // 开始PDF页面
        pdfContext.beginPDFPage(nil)
        
        // 坐标系变换:翻转Y轴,匹配SwiftUI的原点位置
        pdfContext.saveGState()
        pdfContext.translateBy(x: 0, y: pageSize.height)
        pdfContext.scaleBy(x: 1, y: -1)
        
        // 渲染SwiftUI内容到PDF
        let renderer = ImageRenderer(content: GridView(stats: stats).frame(width: pageSize.width))
        renderer.render { size, renderContext in
            renderContext(pdfContext)
        }
        
        pdfContext.restoreGState()
        pdfContext.endPDFPage()
        pdfContext.closePDF()

        return url
    }

    @MainActor
    func updatePreviewImage() {
        // 单独渲染预览图
        let renderer = ImageRenderer(content: GridView(stats: stats))
        renderer.scale = UIScreen.main.scale // 保证预览清晰度
        if let cgImage = renderer.cgImage {
            previewImage = Image(uiImage: UIImage(cgImage: cgImage))
        }
    }
}

struct ExportImagesView_Previews: PreviewProvider {
    static var previews: some View {
        ExportImagesView()
    }
}

关键修改说明

  1. 坐标系修正:

    • 渲染前调用pdfContext.translateBy(x: 0, y: pageSize.height)将原点移到页面底部
    • 调用pdfContext.scaleBy(x: 1, y: -1)沿Y轴翻转,让SwiftUI内容从左上角开始渲染
  2. 预览图生成:

    • 单独用ImageRenderer.cgImage()生成预览图,避免操作PDF上下文
    • 设置renderer.scale = UIScreen.main.scale保证预览图清晰
  3. 代码优化:

    • 简化GridView中的重复frame设置
    • 给表格添加padding避免内容贴边
    • 将PDF渲染和预览更新拆分为两个独立方法,逻辑更清晰

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 22:55:56