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

SwiftUI中如何将嵌套字符串数组渲染为PDF?

SwiftUI嵌套数组渲染PDF:List无法渲染的解决方案

问题描述

尝试在SwiftUI中将包含字符串数组的嵌套数组渲染为PDF文档,但仅能渲染Text组件,无法正常渲染列表。使用的代码如下:

@MainActor
func render() -> URL {
    let renderer = ImageRenderer(content:
                                    List {
        ForEach(docnote.indices, id: \.self) { index in
            VStack(alignment: .leading) {
                ForEach(docnote[index], id: \.self) { info in
                    Text(info)
                }
            }
        }
        .onDelete(perform: deleteStorageItem) // Add onDelete modifier for second List
    }
    )
    let url = URL.documentsDirectory.appending(path: "output.pdf")
    renderer.render { size, context in
        var box = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        guard let pdf = CGContext(url as CFURL, mediaBox: &box, nil) else {
            return
        }
        pdf.beginPDFPage(nil)
        context(pdf)
        pdf.endPDFPage()
        pdf.closePDF()
    }

    return url
}

遇到的错误:

Unable to render flattened version of ListRepresentable<CollectionViewListDataSource, SelectionManagerBox>.
Unable to render flattened version of ListRepresentable<CollectionViewListDataSource, SelectionManagerBox>.
2023-04-15 20:13:41.812705-0400 PrescriptionTracker[4906:1289529] [ShareSheet] Only support loading options for CKShare and SWY types.
2023-04-15 20:13:41.843331-0400 PrescriptionTracker[4906:1289529] [ShareSheet] error fetching item for URL:file:///var/mobile/Containers/Data/Application/739F6D91-7F58-48DA-B3FB-1F9A4F0BE8BA/Documents/output.pdf : Error Domain=NSCocoaErrorDomain Code=256 "The file couldn’t be opened."

问题原因

核心问题是ImageRenderer无法正确渲染SwiftUI的List组件——List底层依赖UIKit的UICollectionView实现,属于带交互的复杂组件,ImageRenderer无法将其扁平化处理生成PDF。而文件打不开的问题,本质是List渲染失败导致PDF文件未被正确写入。

解决方案

用纯SwiftUI的ScrollView + VStack组合替代List,这类轻量视图能被ImageRenderer正常解析。同时优化PDF渲染逻辑,确保文件正确生成:

@MainActor
func render() -> URL {
    // 替换List为ScrollView+VStack,避免UIKit组件渲染限制
    let contentView = ScrollView {
        VStack(spacing: 16) {
            ForEach(docnote.indices, id: \.self) { index in
                VStack(alignment: .leading, spacing: 8) {
                    ForEach(docnote[index], id: \.self) { info in
                        Text(info)
                            .frame(maxWidth: .infinity, alignment: .leading)
                    }
                }
                .padding(.horizontal)
                // 手动添加分隔线模拟List效果(可选)
                Divider()
            }
        }
        .padding()
    }
    
    // 设置固定PDF页面尺寸(A4标准)
    let pageSize = CGSize(width: 595, height: 842)
    let renderer = ImageRenderer(content: contentView)
    renderer.proposedSize = ProposedViewSize(pageSize)
    
    let url = URL.documentsDirectory.appending(path: "output.pdf")
    
    // 先删除旧文件,避免写入冲突
    try? FileManager.default.removeItem(at: url)
    
    renderer.render { size, context in
        var box = CGRect(origin: .zero, size: size)
        guard let pdf = CGContext(url as CFURL, mediaBox: &box, nil) else {
            return
        }
        pdf.beginPDFPage(nil)
        context(pdf)
        pdf.endPDFPage()
        pdf.closePDF()
    }
    
    return url
}

额外说明

  • 如果需要模拟List的分隔线效果,可手动添加Divider()组件
  • 上述代码仅处理单页PDF,若内容过长需要分页,需额外计算内容高度并拆分渲染
  • 移除了onDelete修饰符,PDF属于静态文档,不需要交互功能,编辑逻辑应在生成PDF前处理

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 21:37:47