当使用列表 (List) 时,模型更新后详细视图 (Detail View) 没有被更新 (使用 SwiftUI)。

7

我正在使用List在主视图中显示模型。 当我在详细视图中更新模型时,它在详细视图中没有更新。

当我不使用List时,详细视图会被更新。 我在使用List方面错过了什么?


struct Person: Identifiable {
  var id: UUID
  var name: String
}

class PersonModel: ObservableObject {
  @Published var persons: [Person] = [Person(id: UUID(), name: "Ege")]
}

struct PersonListView: View {
  
  @StateObject private var personModel = PersonModel()
  
  var body: some View {
    NavigationView {
      List {
        ForEach(personModel.persons) { person in
          NavigationLink(destination: PersonDetailView(person: person).environmentObject(personModel)) {
            Text(person.name)
          }
        }
      }
      .navigationTitle("Persons")
    }
  }
}

struct PersonDetailView: View {
  
  let person: Person
  @EnvironmentObject var personModel: PersonModel
  
  var body: some View {
    VStack {
      Text(person.name)
      
      Button(action: {
        let personIndex = personModel.persons.firstIndex(where: { $0.id == person.id })!
        personModel.persons[personIndex].name = "Updated Name"
      }) {
        Text("Update")
      }
    }
    .navigationTitle("Person Detail")
  }
}

我针对List使用的解决方法:

  • 为NavigationLink使用自定义绑定函数。

示例代码:

//1
private func binding(for person: Person) -> Binding<Person> {
  let personIndex = personModel.persons.firstIndex(where: { $0.id == person.id }) ?? 0
  return $personModel.persons[personIndex]
}
//2
NavigationLink(destination: PersonDetailView(person: binding(for: person)))
//3
@Binding var person: Person //DetailView
  • 在详情视图中使用另一个@State,该状态在onAppear方法中使用传递的模型进行初始化。

它对我有效。 - Mecid
2
@Mecid 你确定你只使用了第一个代码片段,因为其他的是解决方法吗?对我来说它不起作用,我很确定。另外,我正在使用 Xcode 12.4。 - egeeke
3个回答

1

我认为,尽管您的解决方案确实起作用,但您想知道发生了什么以及为什么需要解决方案。

当您更新@Published var persons: [Person]数组的值时,您的详细视图会按预期重新呈现。但是,您要求视图打印var person: Person结构体的name值,而这个值没有改变。它仍然与视图最初创建时相同。

如果您替换

Text(person.name)

使用

Text(personModel.persons.first(where: { $0.id == person.id } )!.name)

正如你所期望的那样,它会更新,因为你向Text视图提供了从刚刚更新的实际PersonModel中提取的person的新版本的name值。

因此,List的参与似乎不是问题所在,这就是为什么我没有直接回答你的问题“我对List缺少了什么?”希望这有所帮助!


我知道用那种方式会起作用,但我试图弄清楚/我所问的实际上是为什么如果我不使用List,我的详细视图会重新渲染(尝试将List替换为另一个堆栈,如VStack,您将看到它可以在其他代码不变的情况下工作)。 - egeeke

1
这个问题在Xcode 13 Beta中似乎已经解决了。

-1

编辑

您可以发送手动更改,但是模型需要使用类而不是结构体。 像这样

Button(action: {
    if let personIndex = personModel.persons.firstIndex(where: { $0.id == person.id }) { //<==Here
        personModel.persons[personIndex].name = "Updated Name"
        personModel.objectWillChange.send() //<==Here
    }
    
})

可能的解决方案是更新您的数据并重新分配数组元素。
struct PersonDetailView: View {
    
    let person: Person
    @EnvironmentObject var personModel: PersonModel
    
    var body: some View {
        VStack {
            Text(person.name)
            
            Button(action: {
                if let personIndex = personModel.persons.firstIndex(where: { $0.id == person.id }) { //<==Here
                    personModel.persons[personIndex].name = "Updated Name"
                    personModel.persons[personIndex] = personModel.persons[personIndex]
                }
                
            }) {
                Text("Update")
            }
        }
        .navigationTitle("Person Detail")
    }
}

使用类

class Person: Identifiable {
    var id: UUID
    var name: String
    
    init(id: UUID, name: String) {
        self.id = id
        self.name = name
    }
}


请问为什么在使用List时需要使用类而不是结构体,并使用personModel.objectWillChange.send()通知可观察对象? - egeeke
@egeeke 我使用类(class)是因为我使用引用(reference),这样它会在所有地方更新。而且对于personModel.objectWillChange.send(),这是为了更新整个视图。由于您的详细视图不依赖于任何状态或绑定对象。此外,当您在没有列表的情况下使用时,这意味着您的行(row)是同一视图的一部分。 - Raja Kishan
"personModel.objectWillChange.send() //<==这里" 对于我来说非常有效,和一个非常类似的问题一样。谢谢,Raja Kishan! - ej5607

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接