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

通过Navigation Link传递Binding导致应用崩溃求助

导航栈跳转至具体食材视图时应用冻结无日志的问题

导航栈结构

  • 所有食谱 > 具体食谱 > 食材列表 > 具体食材

相关代码

【所有食谱视图】

struct RecipesScreen: View {
    @EnvironmentObject var sessionData: SessionData
    
    var body: some View {
        NavigationStack {
            List() {
                ForEach($sessionData.familyData.familyRecipes, id: \.self) { $recipe in
                    NavigationLink {
                        EditRecipeScreen(recipe: $recipe)
                    } label: {
                        Text("\(recipe.originalRecipe.title)")
                    }
                }
            }
        }
    }
}

【具体食谱视图】

struct EditRecipeScreen: View {
    @Binding var recipe: FamilyRecipe
    @EnvironmentObject var sessionData: SessionData
    
    var body: some View {
        List {
            // title
            TextField("Title", text: $recipe.originalRecipe.title)
            
            // ingredients >
            NavigationLink {
                IngredientsList(ingredients: $recipe.originalRecipe.ingredients)
            } label: {
                Text("Ingredients")
            }

            // cook >
            NavigationLink {
                InstructionsNavigation(instructions: $recipe.originalRecipe.cookInstructions, minutes: $recipe.originalRecipe.minutesToCook, type: "cook")
            } label: {
                Text("Cooking steps")
            }
            
        }
    }
}

【食材列表视图】

struct IngredientsList: View {
    @EnvironmentObject var sessionData: SessionData
    @Binding var ingredients: [RecipeIngredient]
    
    var body: some View {
        List {
            ForEach($ingredients, id: \.ingredient.id) { $recipeIngredient in
                NavigationLink {
                    IngredientView(recipeIngredient: $recipeIngredient)
                } label: {
                    HStack {
                        Text(recipeIngredient.ingredient.displayName)
                        Spacer()
                        Text("\(recipeIngredient.amount.removeZerosFromEnd()) \(recipeIngredient.measure.description)")
                    }
                }
            }
        }
    }
}

【具体食材视图】

struct IngredientView: View {
    @Binding var recipeIngredient: RecipeIngredient
    
    var body: some View {
            Text("\(recipeIngredient.ingredient.displayName)")
            Text(recipeIngredient.ingredient.measure.description)
    }
}

问题现象

代码简化版功能基本正常,但点击导航链接进入具体食材视图时,手机端应用冻结(无崩溃退出,Xcode无日志输出):

  • 若将具体食材视图中的文本直接放在导航链接的label内,无异常;
  • 若将具体食材视图的recipeIngredient改为@State并以非绑定方式传入,也能正常运行,但无法更新原始食材数据,因此需要解决绑定方式下的冻结问题。

解决方案尝试

1. 检查RecipeIngredient的Equatable实现

SwiftUI的绑定和ForEach依赖稳定的状态标识,若RecipeIngredient未正确实现Equatable,可能导致状态追踪异常。确保RecipeIngredient及其内部类型都实现协议:

struct RecipeIngredient: Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.ingredient.id == rhs.ingredient.id && lhs.amount == rhs.amount && lhs.measure == rhs.measure
    }
    
    var ingredient: Ingredient
    var amount: Double
    var measure: MeasureType
}

struct Ingredient: Equatable, Identifiable {
    let id: UUID
    var displayName: String
    var measure: MeasureType
}

2. 调整导航链接的绑定传递方式

改用NavigationLink(value:)配合navigationDestination的方式传递绑定,避免在闭包中直接创建绑定导致的状态追踪混乱:

struct IngredientsList: View {
    @EnvironmentObject var sessionData: SessionData
    @Binding var ingredients: [RecipeIngredient]
    
    var body: some View {
        List {
            ForEach($ingredients, id: \.ingredient.id) { $recipeIngredient in
                NavigationLink(value: recipeIngredient) {
                    HStack {
                        Text(recipeIngredient.ingredient.displayName)
                        Spacer()
                        Text("\(recipeIngredient.amount.removeZerosFromEnd()) \(recipeIngredient.measure.description)")
                    }
                }
            }
        }
        .navigationDestination(for: RecipeIngredient.self) { targetIngredient in
            if let index = ingredients.firstIndex(where: { $0.ingredient.id == targetIngredient.ingredient.id }) {
                IngredientView(recipeIngredient: $ingredients[index])
            } else {
                Text("Ingredient not found")
            }
        }
    }
}

3. 验证removeZerosFromEnd()方法安全性

自定义的removeZerosFromEnd()方法若存在逻辑漏洞(如无限循环),可能导致线程阻塞。替换为更安全的实现:

extension Double {
    func removeZerosFromEnd() -> String {
        let formatter = NumberFormatter()
        formatter.minimumFractionDigits = 0
        formatter.maximumFractionDigits = 2
        return formatter.string(from: NSNumber(value: self)) ?? "\(self)"
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 08:07:54