英文:
How to update a SwiftUI navigationlink view from an observableObject?
问题
考虑到这个MRE:
struct ContentView: View {
@StateObject var vm = VM()
var body: some View {
NavigationStack {
VStack {
ForEach(vm.list, id: \.self) { int in
NavigationLink {
DetailView(vm: vm, x: int)
} label: {
Text("\(int)")
}
}
Button {
vm.changeFirstElement()
} label: {
Text("change")
}
}
}
}
}
struct DetailView: View {
@ObservedObject var vm: VM
let x: Int
var body: some View {
VStack {
Text("\(x)")
Button {
vm.changeFor(index:x) //how to make this work?
} label: {
Text("change")
}
}
}
}
class VM: ObservableObject {
@Published var list = [1, 2, 3, 4]
func changeFirstElement() {
list[0] = 5
}
}
你可以看到在主视图中,changeFirstElement()
能够正常工作,视图会重新绘制。
但在详细视图内部,尽管它具有@ObservedObject
,但它并不会。我应该如何使数据模型的更改反映在详细视图内部?
英文:
Consider this MRE
struct ContentView: View {
@StateObject var vm = VM()
var body: some View {
NavigationStack {
VStack {
ForEach(vm.list, id: \.self) { int in
NavigationLink {
DetailView(vm: vm, x: int)
} label: {
Text("\(int)")
}
}
Button {
vm.changeFirstElement()
} label: {
Text("change")
}
}
}
}
}
struct DetailView: View {
@ObservedObject var vm: VM
let x: Int
var body: some View {
VStack {
Text("\(x)")
Button {
vm.changeFor(index:x) //how to make this work?
} label: {
Text("change")
}
}
}
}
class VM: ObservableObject {
@Published var list = [1, 2, 3, 4]
func changeFirstElement() {
list[0] = 5
}
}
You can see that in the main view, change() works, the view redraws
But inside the detail view, even though it has observed object it doesn't. How can I make it so that changes in the data model is reflected even inside the Detail View?
答案1
得分: 1
在SwiftUI中,有许多情况(比如ForEach
和List
)需要使用Identifiable
数据。实际上,如果你使用id: self
,通常意味着出现了一些问题。
通过使用一个Identifiable
模型,一切都会“自然而然地运行”:
struct Item: Identifiable {
var id = UUID()
var value: Int
}
struct ContentView: View {
@StateObject var vm = VM()
var body: some View {
NavigationStack {
VStack {
ForEach(vm.list) { item in
NavigationLink {
DetailView(vm: vm, item: item)
} label: {
Text("\(item.value)")
}
}
Button {
vm.change()
} label: {
Text("change")
}
}
}
}
}
struct DetailView: View {
@ObservedObject var vm: VM
let item: Item
var body: some View {
VStack {
Text("\(item.value)")
Button {
vm.change()
} label: {
Text("change")
}
}
}
}
class VM: ObservableObject {
@Published var list: [Item] = [.init(value: 1), .init(value: 2), .init(value: 3)]
func change() {
list[0].value = 5
}
}
请注意,是否真的需要在这里使用VM
也是值得讨论的,你可以直接从ForEach
中使用Binding
。以下是一个在ForEach
中使用Binding
的版本:
struct Item: Identifiable {
var id = UUID()
var value: Int
}
struct ContentView: View {
@StateObject var vm = VM()
var body: some View {
NavigationStack {
VStack {
ForEach($vm.list) { $item in
NavigationLink {
DetailView(item: $item)
} label: {
Text("\(item.value)")
}
}
Button {
vm.list[0].value = 5
} label: {
Text("change")
}
}
}
}
}
struct DetailView: View {
@Binding var item: Item
var body: some View {
VStack {
Text("\(item.value)")
Button {
item.value = 5
} label: {
Text("change")
}
}
}
}
class VM: ObservableObject {
@Published var list: [Item] = [.init(value: 1), .init(value: 2), .init(value: 3)]
}
如果出于某种原因你真的需要使用索引,你可以这样做:
struct Item: Identifiable {
var id = UUID()
var value: Int
}
struct ContentView: View {
@StateObject var vm = VM()
var body: some View {
NavigationStack {
VStack {
ForEach(Array(vm.list.enumerated()), id: \.element.id) { (index, item) in
NavigationLink {
DetailView(vm: vm, item: item, index: index)
} label: {
Text("\(item.value)")
}
}
Button {
vm.changeFirstElement()
} label: {
Text("change")
}
}
}
}
}
struct DetailView: View {
@ObservedObject var vm: VM
let item: Item
let index: Int
var body: some View {
VStack {
Text("\(item.value)")
Button {
vm.changeAtIndex(index)
} label: {
Text("change")
}
}
}
}
class VM: ObservableObject {
@Published var list: [Item] = [.init(value: 1), .init(value: 2), .init(value: 3)]
func changeFirstElement() {
list[0].value = 5
}
func changeAtIndex(_ index: Int) {
list[index].value = 5
}
}
英文:
In SwiftUI, there are many cases (like ForEach
and List
) where it's useful to have Identifiable
data. In fact, if you're using id: self
, it's usually a sign of something going wrong.
By using an Identifiable
model, everything "just works":
struct Item: Identifiable {
var id = UUID()
var value: Int
}
struct ContentView: View {
@StateObject var vm = VM()
var body: some View {
NavigationStack {
VStack {
ForEach(vm.list) { item in
NavigationLink {
DetailView(vm: vm, item: item)
} label: {
Text("\(item.value)")
}
}
Button {
vm.change()
} label: {
Text("change")
}
}
}
}
}
struct DetailView: View {
@ObservedObject var vm: VM
let item: Item
var body: some View {
VStack {
Text("\(item.value)")
Button {
vm.change()
} label: {
Text("change")
}
}
}
}
class VM: ObservableObject {
@Published var list: [Item] = [.init(value: 1), .init(value: 2), .init(value: 3)]
func change() {
list[0].value = 5
}
}
Note that it's also debatable whether you would need the VM
at all here -- you could just use a Binding
from the ForEach
. Here's a version using a Binding
on the ForEach
:
struct Item: Identifiable {
var id = UUID()
var value: Int
}
struct ContentView: View {
@StateObject var vm = VM()
var body: some View {
NavigationStack {
VStack {
ForEach($vm.list) { $item in
NavigationLink {
DetailView(item: $item)
} label: {
Text("\(item.value)")
}
}
Button {
vm.list[0].value = 5
} label: {
Text("change")
}
}
}
}
}
struct DetailView: View {
@Binding var item: Item
var body: some View {
VStack {
Text("\(item.value)")
Button {
item.value = 5
} label: {
Text("change")
}
}
}
}
class VM: ObservableObject {
@Published var list: [Item] = [.init(value: 1), .init(value: 2), .init(value: 3)]
}
And, if for some reason you truly need to use indices, you can do this:
struct Item: Identifiable {
var id = UUID()
var value: Int
}
struct ContentView: View {
@StateObject var vm = VM()
var body: some View {
NavigationStack {
VStack {
ForEach(Array(vm.list.enumerated()), id: \.element.id) { (index, item) in
NavigationLink {
DetailView(vm: vm, item: item, index: index)
} label: {
Text("\(item.value)")
}
}
Button {
vm.changeFirstElement()
} label: {
Text("change")
}
}
}
}
}
struct DetailView: View {
@ObservedObject var vm: VM
let item: Item
let index: Int
var body: some View {
VStack {
Text("\(item.value)")
Button {
vm.changeAtIndex(index)
} label: {
Text("change")
}
}
}
}
class VM: ObservableObject {
@Published var list: [Item] = [.init(value: 1), .init(value: 2), .init(value: 3)]
func changeFirstElement() {
list[0].value = 5
}
func changeAtIndex(_ index: Int) {
list[index].value = 5
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论