I have an edit view that is presented by a NavigationLink. It takes a recipe, which is held in a manager that has an array of them.
App Code (copy pasta should run):
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
}
}
Steps To Reproduce:
If I input a description BEFORE the title, both get updated on the model that is bound to the view. However, if I enter the description AFTER the title, only the title is saved. It doesn't seem to matter whether or not I show/hide keyboard, or change the field focus. Even if I add more properties to the Recipe
model, the same behavior persists for every field after the title
field... help?!
as you mentioned xcode 13 and the new list binding, try this in ContentView:
List($recipeManager.recipes, id: \.id) { $recipe in
NavigationLink(
destination: RecipeDetailView(recipe: $recipe),
label: {
Text(recipe.title.isEmpty ? "New Recipe" : recipe.title)
})
}
and this in RecipeDetailView:
struct RecipeDetailView: View {
@Binding var recipe: Recipe
...
}
Looks like you are not using ".environmentObject(recipeManager)", so remove it.