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

Swift中PDF渲染定位异常问题求助

PDF打印内容重叠问题的修复方案

问题根源

  1. CGContext采用左下角为坐标原点的坐标系,与SwiftUI的左上角原点逻辑冲突,直接使用原始坐标会导致内容错位重叠。
  2. 每次调用translateBy后未恢复上下文状态,后续绘制会继承之前的偏移,导致所有内容堆叠到顶部。
  3. 布局参数与需求不匹配:需求是每页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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 09:15:09