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
}
}
重现步骤:
- 添加新配方,这会将您带到编辑表单。
- 输入标题,然后输入描述,请注意,绑定似乎适用于此上下文中的其他文本字段。
- 保存,返回列表视图。
- 点击新食谱并注意缺少描述。
如果我在标题前输入描述,两者都会在绑定到视图的模型上更新。但是,如果我在标题后输入说明,则只会保存标题。我是否 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)”,所以删除它。
我有一个由 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
}
}
重现步骤:
- 添加新配方,这会将您带到编辑表单。
- 输入标题,然后输入描述,请注意,绑定似乎适用于此上下文中的其他文本字段。
- 保存,返回列表视图。
- 点击新食谱并注意缺少描述。
如果我在标题前输入描述,两者都会在绑定到视图的模型上更新。但是,如果我在标题后输入说明,则只会保存标题。我是否 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)”,所以删除它。