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() } }
关键修改说明
坐标系修正:
- 渲染前调用
pdfContext.translateBy(x: 0, y: pageSize.height)将原点移到页面底部 - 调用
pdfContext.scaleBy(x: 1, y: -1)沿Y轴翻转,让SwiftUI内容从左上角开始渲染
- 渲染前调用
预览图生成:
- 单独用
ImageRenderer.cgImage()生成预览图,避免操作PDF上下文 - 设置
renderer.scale = UIScreen.main.scale保证预览图清晰
- 单独用
代码优化:
- 简化
GridView中的重复frame设置 - 给表格添加
padding避免内容贴边 - 将PDF渲染和预览更新拆分为两个独立方法,逻辑更清晰
- 简化
内容的提问来源于stack exchange,提问作者Whirlwind
相关产品推荐
相关产品推荐

