Swift中PDF渲染定位异常问题求助
PDF打印内容重叠问题的修复方案
问题根源
- CGContext采用左下角为坐标原点的坐标系,与SwiftUI的左上角原点逻辑冲突,直接使用原始坐标会导致内容错位重叠。
- 每次调用
translateBy后未恢复上下文状态,后续绘制会继承之前的偏移,导致所有内容堆叠到顶部。 - 布局参数与需求不匹配:需求是每页15个(3列×5行)子项,但代码中设置了4列,导致计算逻辑混乱。
修复步骤及代码修改
以下是修正后的完整代码,关键修改点已标注:
@MainActor func render(viewsPerPage: Int) -> URL { let eventsArray: [Event] = events.map { $0 } let url = URL.documentsDirectory.appending(path: "\(recipient.wrappedFirstName)-\(recipient.wrappedLastName)-cards.pdf") var pageSize = CGRect(x: 0, y: 0, width: 612, height: 792) guard let pdfOutput = CGContext(url as CFURL, mediaBox: &pageSize, nil) else { return url } // 修正布局参数:3列×5行,匹配每页15个子项的需求 let numberOfPages = Int((events.count + viewsPerPage - 1) / viewsPerPage) let columnWidth = pageSize.width / 3 let rowHeight = (pageSize.height - 90 - 20 - 20) / 5 // 扣除页眉、页脚和上下间距 let xColumn = [0.0, columnWidth, columnWidth * 2] // 3列起始x坐标 let yRow = [pageSize.height - 90 - 10, pageSize.height - 90 - 10 - rowHeight, pageSize.height - 90 - 10 - rowHeight*2, pageSize.height - 90 - 10 - rowHeight*3, pageSize.height - 90 - 10 - rowHeight*4] // 5行起始y坐标(扣除页眉高度和间距) let viewsPerRow = 3 let rowsPerPage = 5 let spacing = 10.0 for pageIndex in 0..<numberOfPages { pdfOutput.beginPDFPage(nil) // --- 渲染页眉 --- let rendererTop = ImageRenderer(content: Color.red.frame(width: pageSize.width, height: 90)) pdfOutput.saveGState() // 保存上下文初始状态 rendererTop.render { size, renderTop in // 转换到页眉位置:顶部向下偏移spacing pdfOutput.translateBy(x: 0, y: pageSize.height - size.height - spacing) renderTop(pdfOutput) } pdfOutput.restoreGState() // 恢复上下文,避免影响后续绘制 // --- 渲染子项网格 --- let startIndex = pageIndex * viewsPerPage let endIndex = min(startIndex + viewsPerPage, eventsArray.count) for row in 0..<rowsPerPage { for col in 0..<viewsPerRow { let index = startIndex + row * viewsPerRow + col guard index < endIndex, let event = eventsArray[safe: index] else { continue } let renderBody = ImageRenderer(content: Text("Event[\(index)]").frame(width: columnWidth - spacing, height: rowHeight - spacing).background(Color.blue)) pdfOutput.saveGState() renderBody.render { size, renderBody in // 计算子项位置:基于CGContext左下原点调整y坐标 pdfOutput.translateBy(x: xColumn[col] + spacing/2, y: yRow[row]) renderBody(pdfOutput) } pdfOutput.restoreGState() } } // --- 渲染页脚 --- let rendererBottom = ImageRenderer(content: Text("Page \((pageIndex + 1).formatted()) of \(numberOfPages.formatted())").frame(width: pageSize.width, height: 20).background(Color.yellow)) pdfOutput.saveGState() rendererBottom.render { size, renderBottom in // 页脚在底部,y轴偏移为size.height(适配CGContext左下原点) pdfOutput.translateBy(x: 0, y: size.height) renderBottom(pdfOutput) } pdfOutput.restoreGState() pdfOutput.endPDFPage() } pdfOutput.closePDF() return url }
关键修改说明
- 上下文状态管理:每次渲染视图前后调用
saveGState()和restoreGState(),确保每次绘制的坐标偏移不会影响后续内容。 - 坐标系适配:针对CGContext的左下原点特性,重新计算所有元素的y轴坐标,确保页眉在页面顶部、子项网格占据中间区域、页脚在底部。
- 布局参数对齐需求:将列数改为3,匹配"每页15个子项(3×5)"的需求,同时调整行高计算逻辑,扣除页眉、页脚和间距,让子项均匀分布。
- 间距优化:在子项的x/y坐标中加入间距,避免内容边缘贴合或重叠。
内容的提问来源于stack exchange,提问作者Michael Rowe
相关产品推荐
相关产品推荐

