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

Swift项目View转PDF失败,报导航栈无接口惯用语错误

解决SwiftUI中View转PDF的两个关键错误

错误原因分析

  1. NavigationStack崩溃错误:No interface idiom was found when creating the navigation stack.
    当用ImageRenderer渲染包含NavigationStack的View时,该View不在正常的SwiftUI视图层级中,缺少系统必要的环境上下文,导致崩溃。

  2. SwiftData环境缺失错误:Set a .modelContext in view's environment to use Query
    生成PDF时新建的RecipeView没有继承父视图的SwiftData环境,@Query依赖.modelContext才能工作,因此报错。

解决方案

1. 重构View结构,拆分内容与导航层

将食谱展示内容抽离为独立子View,PDF渲染时只渲染内容部分,避免不必要的NavigationStack。同时优化数据传递逻辑,直接传递目标食谱对象,避免遍历开销。

2. 手动注入SwiftData环境

在生成PDF时,从当前视图环境获取modelContext,查询目标食谱后直接传递给内容View,不再依赖@Query的环境注入。

3. 修复步骤计数逻辑

用enumerated()获取步骤索引,替代原@State变量,避免ScrollView视图复用导致的计数错误。


修改后的完整代码

import SwiftUI
import SwiftData
import PDFKit

@MainActor
struct ContentView: View {
    @State private var pdfURL: URL?
    var id = ""
    @Environment(\.modelContext) private var modelContext

    var body: some View {
        NavigationStack {
            VStack{
                RecipeView(id: id)
                Button("Generate PDF") {
                    pdfURL = generatePDF()
                }
                .padding()
                if let pdfURL = pdfURL {
                    ShareLink("Export PDF", item: pdfURL)
                }
            }
        }
    }

    func generatePDF() -> URL {
        // 查询目标食谱
        guard let targetRecipe = try? modelContext.fetch(
            FetchDescriptor<Recipe>(predicate: \.id == id)
        ).first else {
            return URL.documentsDirectory.appending(path: "empty.pdf")
        }

        // 初始化用于PDF渲染的内容View
        let pdfContentView = RecipeDetailView(recipe: targetRecipe)
        let url = URL.documentsDirectory.appending(path: "generatedPDF.pdf")
        let renderer = ImageRenderer(content: pdfContentView)

        renderer.render { size, context in
            var pdfDimension = CGRect(x: 0, y: 0, width: size.width, height: size.height)
            guard let pdf = CGContext(url as CFURL, mediaBox: &pdfDimension, nil) else {
                return
            }
            pdf.beginPDFPage(nil)
            context(pdf)
            pdf.endPDFPage()
            pdf.closePDF()
        }

        return url
    }
}

@MainActor
struct RecipeView: View {
    @Query(sort: \Recipe.title) var recipes: [Recipe]
    var id = ""

    var body: some View {
        NavigationStack{
            if let targetRecipe = recipes.first(where: { $0.id == id }) {
                RecipeDetailView(recipe: targetRecipe)
            } else {
                Text("Rezept nicht gefunden")
            }
        }
    }
}

@MainActor
struct RecipeDetailView: View {
    let recipe: Recipe

    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 20) {
                Text(recipe.title)
                    .font(.largeTitle)
                    .fontWeight(.bold)
                
                Text("Zutaten:")
                    .font(.headline)
                    .fontWeight(.bold)
                ForEach(recipe.amount.indices, id: \.self) { index in
                    Text("• \(recipe.amount[index]) \(recipe.ingredients[index])")
                }
                
                Text("Zubereitung:")
                    .font(.headline)
                    .fontWeight(.bold)
                ForEach(recipe.steps.enumerated(), id: \.element) { index, step in
                    Text("Schritt \(index + 1)")
                        .font(.headline)
                        .fontWeight(.bold)
                    Text("• \(step)")
                }
                
                Spacer()
            }
            .padding()
        }
    }
}

#Preview {
    ContentView(id: "")
        .modelContainer(for: Recipe.self)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 05:08:11