如何从observableObject更新SwiftUI navigationlink视图?

huangapple go评论73阅读模式
英文:

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中,有许多情况(比如ForEachList)需要使用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
    }
}

huangapple
  • 本文由 发表于 2023年7月13日 21:39:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76680044.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定