Reputation: 447
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?!
Upvotes: 3
Views: 1089
Reputation: 36343
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.
Upvotes: 2
Reputation: 36343
one key element missing, is your code for the recipe manager. Using the following test code for RecipeManager, I got the TextFields to behave as expected:
@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())
}
}
Upvotes: 0