SwiftUI 绑定到列表元素仅更新以正确顺序输入的值

SwiftUI Binding to a list element only updates values input in the right order

我有一个由 NavigationLink 呈现的编辑视图。它需要一个菜谱,该菜谱保存在一个管理器中,该管理器有一个菜谱数组。

应用代码(复制面食应该运行):

import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

// VIEWS
//
struct ContentView: View {
    @StateObject var recipeManager = RecipeManager()
    
    @State var editingRecipeIndex: Int?
    @State var showEditView = false

    var body: some View {
        NavigationView {
            ZStack {
                if let index = editingRecipeIndex {
                    // THIS LINK SEEMS TO NOT HOOK UP CORRECTLY ***
                    NavigationLink(destination: RecipeEditView(recipe: $recipeManager.recipes[index]), isActive: $showEditView, label: {
                        EmptyView()
                    }).buttonStyle(PlainButtonStyle())
                }
                
                List(recipeManager.recipes, id: \.self) { recipe in
                    NavigationLink(
                        destination: RecipeDetailView(recipe: recipe),
                        label: {
                            Text(recipe.title.isEmpty ? "New Recipe" : recipe.title)
                        })
                }
                .navigationTitle("My Recipes")
                .listStyle(GroupedListStyle())
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button(action: {
                            recipeManager.recipes.append(Recipe())
                            editingRecipeIndex = recipeManager.recipes.count - 1
                            showEditView = true
                        }, label: {
                            Image(systemName: "plus")
                        })
                    }
                }
            }
           
        }
        .environmentObject(recipeManager)
    }
}

struct RecipeDetailView: View {
    var recipe: Recipe
    
    var body: some View {
        VStack {
            Text(recipe.title)
                .font(.title)
                .padding(.top)
            
            Text(recipe.description)
                .fixedSize(horizontal: false, vertical: true)
        }
    }
}

struct RecipeEditView: View {
    @Binding var recipe: Recipe
    
    @Environment(\.presentationMode) var presentationMode
    
    var body: some View {
        Form {
            TextField("Enter your recipe title", text: $recipe.title)
            TextField("Enter a description", text: $recipe.description)
            
            Text("Title: \(recipe.title)")
            Text("Description: \(recipe.description)")
            
            Button("Save") {
                presentationMode.wrappedValue.dismiss()
            }
        }
    }
}

// MODELS
//
class RecipeManager: ObservableObject {
    @Published var recipes: [Recipe] = [
        Recipe(title: "one", description: "one-one"),
        Recipe(title: "two", description: "two-two"),
        Recipe(title: "three", description: "three-three")
    ]
}

struct Recipe: Identifiable, Hashable, Equatable {
    let id: UUID
    var imageName: String
    var title: String
    var description: String
    
    var steps: [String]  // [RecipeStep]
    
    static func == (lhs: Recipe, rhs: Recipe) -> Bool {
        return lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
    init(id: UUID = UUID(), imageName: String = "croissant", title: String = "", description: String = "", steps: [String] = []) {
        self.id = id
        self.imageName = imageName
        self.title = title
        self.description = description
        self.steps = steps
    }
}

重现步骤:

  1. 添加新配方,这会将您带到编辑表单。
  2. 输入标题,然后输入描述,请注意,绑定似乎适用于此上下文中的其他文本字段。
  3. 保存,返回列表视图。
  4. 点击新食谱并注意缺少描述。

如果我在标题前输入描述,两者都会在绑定到视图的模型上更新。但是,如果我在标题后输入说明,则只会保存标题。我是否 show/hide 键盘或更改字段焦点似乎并不重要。即使我向 Recipe 模型添加更多属性,在 title 字段之后的每个字段仍然存在相同的行为...帮助?!

缺少一个关键元素,是您的食谱管理器代码。使用 RecipeManager 的以下测试代码,我得到了 TextFields 的行为符合预期:

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct Recipe: Identifiable, Hashable, Equatable {
    let id: UUID
    var imageName: String
    var title: String
    var description: String
    
    var steps: [String]  // [RecipeStep]
    
    static func == (lhs: Recipe, rhs: Recipe) -> Bool {
        return lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
    init(id: UUID = UUID(), imageName: String = "croissant", title: String = "", description: String = "", steps: [String] = []) {
        self.id = id
        self.imageName = imageName
        self.title = title
        self.description = description
        self.steps = steps
    }
}

struct RecipeEditView: View {
    @Binding var recipe: Recipe
    
    @Environment(\.presentationMode) var presentationMode
    
    var body: some View {
        Form {
            TextField("Enter your recipe title", text: $recipe.title)
            TextField("Enter a description", text: $recipe.description)
            
            Text("Title: \(recipe.title)")
            Text("Description: \(recipe.description)")
            
            Button("Save") {
                presentationMode.wrappedValue.dismiss()
            }
        }
    }
}

class RecipeManager: ObservableObject {
    @Published var recipes: [Recipe] = [
        Recipe(title: "one", description: "one-one"),
        Recipe(title: "two", description: "two-two"),
        Recipe(title: "three", description: "three-three")
    ]
}

struct ContentView: View {
    @StateObject var recipeManager = RecipeManager()

    var body: some View {
        NavigationView {
            List {
                ForEach(recipeManager.recipes.indices) { index in
                    NavigationLink(recipeManager.recipes[index].title,
                                   destination: RecipeEditView(recipe: $recipeManager.recipes[index]))
                        .tag(index)
                }
            }
        }.navigationViewStyle(StackNavigationViewStyle())
    }
}

正如您提到的 xcode 13 和新的列表绑定,在 ContentView 中试试这个:

            List($recipeManager.recipes, id: \.id) { $recipe in
                NavigationLink(
                    destination: RecipeDetailView(recipe: $recipe),
                    label: {
                        Text(recipe.title.isEmpty ? "New Recipe" : recipe.title)
                    })
            }

RecipeDetailView 中的这个:

struct RecipeDetailView: View {
     @Binding var recipe: Recipe
     ...
     }

看起来你没有使用“.environmentObject(recipeManager)”,所以删除它。