Swift项目View转PDF失败,报导航栈无接口惯用语错误
解决SwiftUI中View转PDF的两个关键错误
错误原因分析
NavigationStack崩溃错误:
No interface idiom was found when creating the navigation stack.
当用ImageRenderer渲染包含NavigationStack的View时,该View不在正常的SwiftUI视图层级中,缺少系统必要的环境上下文,导致崩溃。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
相关产品推荐
相关产品推荐

