如何从observableObject更新SwiftUI navigationlink视图?

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

How to update a SwiftUI navigationlink view from an observableObject?

问题

考虑到这个MRE:

  1. struct ContentView: View {
  2. @StateObject var vm = VM()
  3. var body: some View {
  4. NavigationStack {
  5. VStack {
  6. ForEach(vm.list, id: \.self) { int in
  7. NavigationLink {
  8. DetailView(vm: vm, x: int)
  9. } label: {
  10. Text("\(int)")
  11. }
  12. }
  13. Button {
  14. vm.changeFirstElement()
  15. } label: {
  16. Text("change")
  17. }
  18. }
  19. }
  20. }
  21. }
  22. struct DetailView: View {
  23. @ObservedObject var vm: VM
  24. let x: Int
  25. var body: some View {
  26. VStack {
  27. Text("\(x)")
  28. Button {
  29. vm.changeFor(index:x) //how to make this work?
  30. } label: {
  31. Text("change")
  32. }
  33. }
  34. }
  35. }
  36. class VM: ObservableObject {
  37. @Published var list = [1, 2, 3, 4]
  38. func changeFirstElement() {
  39. list[0] = 5
  40. }
  41. }

你可以看到在主视图中,changeFirstElement() 能够正常工作,视图会重新绘制。

但在详细视图内部,尽管它具有@ObservedObject,但它并不会。我应该如何使数据模型的更改反映在详细视图内部?

英文:

Consider this MRE

  1. struct ContentView: View {
  2. @StateObject var vm = VM()
  3. var body: some View {
  4. NavigationStack {
  5. VStack {
  6. ForEach(vm.list, id: \.self) { int in
  7. NavigationLink {
  8. DetailView(vm: vm, x: int)
  9. } label: {
  10. Text("\(int)")
  11. }
  12. }
  13. Button {
  14. vm.changeFirstElement()
  15. } label: {
  16. Text("change")
  17. }
  18. }
  19. }
  20. }
  21. }
  22. struct DetailView: View {
  23. @ObservedObject var vm: VM
  24. let x: Int
  25. var body: some View {
  26. VStack {
  27. Text("\(x)")
  28. Button {
  29. vm.changeFor(index:x) //how to make this work?
  30. } label: {
  31. Text("change")
  32. }
  33. }
  34. }
  35. }
  36. class VM: ObservableObject {
  37. @Published var list = [1, 2, 3, 4]
  38. func changeFirstElement() {
  39. list[0] = 5
  40. }
  41. }

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模型,一切都会“自然而然地运行”:

  1. struct Item: Identifiable {
  2. var id = UUID()
  3. var value: Int
  4. }
  5. struct ContentView: View {
  6. @StateObject var vm = VM()
  7. var body: some View {
  8. NavigationStack {
  9. VStack {
  10. ForEach(vm.list) { item in
  11. NavigationLink {
  12. DetailView(vm: vm, item: item)
  13. } label: {
  14. Text("\(item.value)")
  15. }
  16. }
  17. Button {
  18. vm.change()
  19. } label: {
  20. Text("change")
  21. }
  22. }
  23. }
  24. }
  25. }
  26. struct DetailView: View {
  27. @ObservedObject var vm: VM
  28. let item: Item
  29. var body: some View {
  30. VStack {
  31. Text("\(item.value)")
  32. Button {
  33. vm.change()
  34. } label: {
  35. Text("change")
  36. }
  37. }
  38. }
  39. }
  40. class VM: ObservableObject {
  41. @Published var list: [Item] = [.init(value: 1), .init(value: 2), .init(value: 3)]
  42. func change() {
  43. list[0].value = 5
  44. }
  45. }

请注意,是否真的需要在这里使用VM也是值得讨论的,你可以直接从ForEach中使用Binding。以下是一个在ForEach中使用Binding的版本:

  1. struct Item: Identifiable {
  2. var id = UUID()
  3. var value: Int
  4. }
  5. struct ContentView: View {
  6. @StateObject var vm = VM()
  7. var body: some View {
  8. NavigationStack {
  9. VStack {
  10. ForEach($vm.list) { $item in
  11. NavigationLink {
  12. DetailView(item: $item)
  13. } label: {
  14. Text("\(item.value)")
  15. }
  16. }
  17. Button {
  18. vm.list[0].value = 5
  19. } label: {
  20. Text("change")
  21. }
  22. }
  23. }
  24. }
  25. }
  26. struct DetailView: View {
  27. @Binding var item: Item
  28. var body: some View {
  29. VStack {
  30. Text("\(item.value)")
  31. Button {
  32. item.value = 5
  33. } label: {
  34. Text("change")
  35. }
  36. }
  37. }
  38. }
  39. class VM: ObservableObject {
  40. @Published var list: [Item] = [.init(value: 1), .init(value: 2), .init(value: 3)]
  41. }

如果出于某种原因你真的需要使用索引,你可以这样做:

  1. struct Item: Identifiable {
  2. var id = UUID()
  3. var value: Int
  4. }
  5. struct ContentView: View {
  6. @StateObject var vm = VM()
  7. var body: some View {
  8. NavigationStack {
  9. VStack {
  10. ForEach(Array(vm.list.enumerated()), id: \.element.id) { (index, item) in
  11. NavigationLink {
  12. DetailView(vm: vm, item: item, index: index)
  13. } label: {
  14. Text("\(item.value)")
  15. }
  16. }
  17. Button {
  18. vm.changeFirstElement()
  19. } label: {
  20. Text("change")
  21. }
  22. }
  23. }
  24. }
  25. }
  26. struct DetailView: View {
  27. @ObservedObject var vm: VM
  28. let item: Item
  29. let index: Int
  30. var body: some View {
  31. VStack {
  32. Text("\(item.value)")
  33. Button {
  34. vm.changeAtIndex(index)
  35. } label: {
  36. Text("change")
  37. }
  38. }
  39. }
  40. }
  41. class VM: ObservableObject {
  42. @Published var list: [Item] = [.init(value: 1), .init(value: 2), .init(value: 3)]
  43. func changeFirstElement() {
  44. list[0].value = 5
  45. }
  46. func changeAtIndex(_ index: Int) {
  47. list[index].value = 5
  48. }
  49. }
英文:

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":

  1. struct Item: Identifiable {
  2. var id = UUID()
  3. var value: Int
  4. }
  5. struct ContentView: View {
  6. @StateObject var vm = VM()
  7. var body: some View {
  8. NavigationStack {
  9. VStack {
  10. ForEach(vm.list) { item in
  11. NavigationLink {
  12. DetailView(vm: vm, item: item)
  13. } label: {
  14. Text("\(item.value)")
  15. }
  16. }
  17. Button {
  18. vm.change()
  19. } label: {
  20. Text("change")
  21. }
  22. }
  23. }
  24. }
  25. }
  26. struct DetailView: View {
  27. @ObservedObject var vm: VM
  28. let item: Item
  29. var body: some View {
  30. VStack {
  31. Text("\(item.value)")
  32. Button {
  33. vm.change()
  34. } label: {
  35. Text("change")
  36. }
  37. }
  38. }
  39. }
  40. class VM: ObservableObject {
  41. @Published var list: [Item] = [.init(value: 1), .init(value: 2), .init(value: 3)]
  42. func change() {
  43. list[0].value = 5
  44. }
  45. }

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:

  1. struct Item: Identifiable {
  2. var id = UUID()
  3. var value: Int
  4. }
  5. struct ContentView: View {
  6. @StateObject var vm = VM()
  7. var body: some View {
  8. NavigationStack {
  9. VStack {
  10. ForEach($vm.list) { $item in
  11. NavigationLink {
  12. DetailView(item: $item)
  13. } label: {
  14. Text("\(item.value)")
  15. }
  16. }
  17. Button {
  18. vm.list[0].value = 5
  19. } label: {
  20. Text("change")
  21. }
  22. }
  23. }
  24. }
  25. }
  26. struct DetailView: View {
  27. @Binding var item: Item
  28. var body: some View {
  29. VStack {
  30. Text("\(item.value)")
  31. Button {
  32. item.value = 5
  33. } label: {
  34. Text("change")
  35. }
  36. }
  37. }
  38. }
  39. class VM: ObservableObject {
  40. @Published var list: [Item] = [.init(value: 1), .init(value: 2), .init(value: 3)]
  41. }

And, if for some reason you truly need to use indices, you can do this:

  1. struct Item: Identifiable {
  2. var id = UUID()
  3. var value: Int
  4. }
  5. struct ContentView: View {
  6. @StateObject var vm = VM()
  7. var body: some View {
  8. NavigationStack {
  9. VStack {
  10. ForEach(Array(vm.list.enumerated()), id: \.element.id) { (index, item) in
  11. NavigationLink {
  12. DetailView(vm: vm, item: item, index: index)
  13. } label: {
  14. Text("\(item.value)")
  15. }
  16. }
  17. Button {
  18. vm.changeFirstElement()
  19. } label: {
  20. Text("change")
  21. }
  22. }
  23. }
  24. }
  25. }
  26. struct DetailView: View {
  27. @ObservedObject var vm: VM
  28. let item: Item
  29. let index: Int
  30. var body: some View {
  31. VStack {
  32. Text("\(item.value)")
  33. Button {
  34. vm.changeAtIndex(index)
  35. } label: {
  36. Text("change")
  37. }
  38. }
  39. }
  40. }
  41. class VM: ObservableObject {
  42. @Published var list: [Item] = [.init(value: 1), .init(value: 2), .init(value: 3)]
  43. func changeFirstElement() {
  44. list[0].value = 5
  45. }
  46. func changeAtIndex(_ index: Int) {
  47. list[index].value = 5
  48. }
  49. }

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:

确定