SwiftUI的ForEach视图在底层@StateObject字典更新时不会更新。

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

SwiftUI ForEach View does not update when underlying @StateObject dictionary is updated

问题

以下是翻译好的部分:

"Here are the pieces. I'm trying to have the UI button update the data in Data and have SwiftUI automatically update another sibling View (that is based on the Data model). The Data model dict is updated, but the Cell view in ForEach does not update."

  1. // 主视图 (ContentView)
  2. import SwiftUI
  3. struct ContentView: View {
  4. @StateObject private var data = Data()
  5. @State private var cells: [Int: [Cell]] = [:]
  6. var body: some View {
  7. VStack {
  8. Text("Debug me")
  9. if let items = cells[0] {
  10. ForEach(items) { item in
  11. item
  12. }
  13. }
  14. }
  15. .onAppear {
  16. setup()
  17. }
  18. }
  19. private func setup() {
  20. var newData: [Cell] = []
  21. newData.append(Cell(key: "A", data: data, text: "1"))
  22. newData.append(Cell(key: "B", data: data, text: "2"))
  23. newData.append(Cell(key: "C", data: data, text: "3"))
  24. cells[0] = newData
  25. }
  26. }
  1. // 子视图 (Cell)
  2. import SwiftUI
  3. struct Cell: View, Hashable, Identifiable {
  4. @State private var key = ""
  5. @ObservedObject private var data: Data
  6. @State var text: String
  7. init(key: String, data: Data, text: String) {
  8. self.key = key
  9. self.data = data
  10. self.text = text
  11. }
  12. // Identifiable
  13. var id: String {
  14. return "\(key)\(text)"
  15. }
  16. // Hashable
  17. func hash(into hasher: inout Hasher) {
  18. hasher.combine(key)
  19. hasher.combine(text)
  20. }
  21. // Equatable
  22. static func ==(lhs: Cell, rhs: Cell) -> Bool {
  23. return lhs.key == rhs.key && lhs.text == rhs.text
  24. }
  25. var body: some View {
  26. HStack {
  27. Text(key)
  28. Text(" / ")
  29. Text(text)
  30. Button(action: confirm) {
  31. Label("", systemImage: "checkmark")
  32. }
  33. }
  34. }
  35. private func confirm() {
  36. // 构造的示例
  37. // 这不起作用
  38. data.updateValue(key: "C", value: "updated123")
  39. }
  40. }
  1. // 数据类 (Data)
  2. import SwiftUI
  3. class Data: ObservableObject {
  4. @Published var data: [String:String]
  5. init() {
  6. data = [:]
  7. }
  8. func updateValue(key: String, value: String) {
  9. data[key] = value
  10. objectWillChange.send()
  11. }
  12. }
英文:

Here are the pieces. I'm trying to have the UI button update the data in Data and have SwiftUI automatically update another sibling View (that is based on the Data model). The Data model dict is updated, but the Cell view in ForEach does not update.

  1. // main view (ContentView)
  2. import SwiftUI
  3. struct ContentView: View {
  4. @StateObject private var data = Data()
  5. @State private var cells: [Int: [Cell]] = [:]
  6. var body: some View {
  7. VStack {
  8. Text("Debug me")
  9. if let items = cells[0] {
  10. ForEach(items) { item in
  11. item
  12. }
  13. }
  14. }
  15. .onAppear {
  16. setup()
  17. }
  18. }
  19. private func setup() {
  20. var newData: [Cell] = []
  21. newData.append(Cell(key: "A", data: data, text: "1"))
  22. newData.append(Cell(key: "B", data: data, text: "2"))
  23. newData.append(Cell(key: "C", data: data, text: "3"))
  24. cells[0] = newData
  25. }
  26. }
  1. // child view (Cell)
  2. import SwiftUI
  3. struct Cell: View, Hashable, Identifiable {
  4. @State private var key = ""
  5. @ObservedObject private var data: Data
  6. @State var text: String
  7. init(key: String, data: Data, text: String) {
  8. self.key = key
  9. self.data = data
  10. self.text = text
  11. }
  12. // Identifiable
  13. var id: String {
  14. return "\(key)\(text)"
  15. }
  16. // Hashable
  17. func hash(into hasher: inout Hasher) {
  18. hasher.combine(key)
  19. hasher.combine(text)
  20. }
  21. // Equatable
  22. static func ==(lhs: Cell, rhs: Cell) -> Bool {
  23. return lhs.key == rhs.key && lhs.text == rhs.text
  24. }
  25. var body: some View {
  26. HStack {
  27. Text(key)
  28. Text(" / ")
  29. Text(text)
  30. Button(action: confirm) {
  31. Label("", systemImage: "checkmark")
  32. }
  33. }
  34. }
  35. private func confirm() {
  36. // contrived example
  37. // this does not work
  38. data.updateValue(key: "C", value: "updated123")
  39. }
  40. }
  1. // data class (Data)
  2. import SwiftUI
  3. class Data: ObservableObject {
  4. @Published var data: [String:String]
  5. init() {
  6. data = [:]
  7. }
  8. func updateValue(key: String, value: String) {
  9. data[key] = value
  10. objectWillChange.send()
  11. }
  12. }

答案1

得分: 0

正如我在上面的注释中所写,Cell 视图不使用观察对象,因此它和其他属性是完全独立的。

以下是我修改后的 Cell 版本,我将 text 改为了计算属性,因为它实际上代表了从观察对象的发布属性中获取的值,给定 key 值。

  1. struct Cell: View, Hashable, Identifiable {
  2. private let key: String
  3. @ObservedObject private var data: DataModel
  4. var text: String { data.data[key] ?? "" }
  5. init(key: String, data: DataModel, text: String) {
  6. self.key = key
  7. data.data[key] = text
  8. self.data = data
  9. }
  10. var body: some View {
  11. HStack {
  12. Text(key)
  13. Text(" / ")
  14. Text(text)
  15. Button(action: { confirm(key) }) {
  16. Label("", systemImage: "checkmark")
  17. }
  18. }
  19. }
  20. private func confirm(_ key: String) {
  21. data.updateValue(key: key, value: "\(Int.random(in: 1...100))")
  22. }
  23. var id: String { "\(key)" }
  24. func hash(into hasher: inout Hasher) { hasher.combine(key) }
  25. static func ==(lhs: Cell, rhs: Cell) -> Bool { lhs.key == rhs.key }
  26. }

请注意,我将 key 改为了常量,因为它是每个单元格的唯一标识符,并且在符合协议时,我还删除了 text。另外,我将 Data 重命名为 DataModel

英文:

As I wrote in the comment above the Cell view doesn't make use of the observed object so that it and the other properties are completely independent of each other.

Below is my version of Cell where I have made text into a computed property since this is actually what it represent, the value you get from the observed objects published property given the key value.

  1. struct Cell: View, Hashable, Identifiable {
  2. @State private var key = ""
  3. @ObservedObject private var data: DataModel
  4. var text: String { data.data[key] ?? "" }
  5. init(key: String, data: DataModel, text: String) {
  6. self.key = key
  7. data.data[key] = text
  8. self.data = data
  9. }
  10. var body: some View {
  11. HStack {
  12. Text(key)
  13. Text(" / ")
  14. Text(text)
  15. Button(action: { confirm(key) }) {
  16. Label("", systemImage: "checkmark")
  17. }
  18. }
  19. }
  20. private func confirm(_ key: String) {
  21. data.updateValue(key: key, value: "\(Int.random(in: 1...100))")
  22. }
  23. var id: String { "\(key)" }
  24. func hash(into hasher: inout Hasher) { hasher.combine(key) }
  25. static func ==(lhs: Cell, rhs: Cell) -> Bool { lhs.key == rhs.key }
  26. }

Note that I made key into a constant since it is the unique identifier for each cell and that I also removed text when conforming to the protocols because key is unique. And I renamed Data to DataModel

huangapple
  • 本文由 发表于 2023年3月7日 20:08:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/75661792.html
匿名

发表评论

匿名网友

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

确定