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

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

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

// 主视图 (ContentView)

import SwiftUI

struct ContentView: View {

    @StateObject private var data = Data()
    @State private var cells: [Int: [Cell]] = [:]

    var body: some View {
        VStack {
            Text("Debug me")

            if let items = cells[0] {
                ForEach(items) { item in
                    item
                }
            }
        }
        .onAppear {
            setup()
        }
    }

    private func setup() {
        var newData: [Cell] = []
        newData.append(Cell(key: "A", data: data, text: "1"))
        newData.append(Cell(key: "B", data: data, text: "2"))
        newData.append(Cell(key: "C", data: data, text: "3"))
        cells[0] = newData
    }
}
// 子视图 (Cell)

import SwiftUI

struct Cell: View, Hashable, Identifiable {
    @State private var key = ""
    @ObservedObject private var data: Data

    @State var text: String

    init(key: String, data: Data, text: String) {
        self.key = key
        self.data = data
        self.text = text
    }

    // Identifiable
    var id: String {
        return "\(key)\(text)"
    }

    // Hashable
    func hash(into hasher: inout Hasher) {
        hasher.combine(key)
        hasher.combine(text)
    }

    // Equatable
    static func ==(lhs: Cell, rhs: Cell) -> Bool {
        return lhs.key == rhs.key && lhs.text == rhs.text
    }

    var body: some View {
        HStack {
            Text(key)
            Text(" / ")
            Text(text)
            Button(action: confirm) {
                Label("", systemImage: "checkmark")
            }
        }
    }

    private func confirm() {
        // 构造的示例
        // 这不起作用
        data.updateValue(key: "C", value: "updated123")
    }

}
// 数据类 (Data)

import SwiftUI

class Data: ObservableObject {

    @Published var data: [String:String]

    init() {
        data = [:]
    }

    func updateValue(key: String, value: String) {
        data[key] = value
        objectWillChange.send()
    }

}
英文:

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.

// main view (ContentView)

import SwiftUI

struct ContentView: View {

    @StateObject private var data = Data()
    @State private var cells: [Int: [Cell]] = [:]

    var body: some View {
        VStack {
            Text("Debug me")

            if let items = cells[0] {
                ForEach(items) { item in
                    item
                }
            }
        }
        .onAppear {
            setup()
        }
    }

    private func setup() {
        var newData: [Cell] = []
        newData.append(Cell(key: "A", data: data, text: "1"))
        newData.append(Cell(key: "B", data: data, text: "2"))
        newData.append(Cell(key: "C", data: data, text: "3"))
        cells[0] = newData
    }
}

// child view (Cell)

import SwiftUI

struct Cell: View, Hashable, Identifiable {
    @State private var key = ""
    @ObservedObject private var data: Data

    @State var text: String

    init(key: String, data: Data, text: String) {
        self.key = key
        self.data = data
        self.text = text
    }

    // Identifiable
    var id: String {
        return "\(key)\(text)"
    }

    // Hashable
    func hash(into hasher: inout Hasher) {
        hasher.combine(key)
        hasher.combine(text)
    }

    // Equatable
    static func ==(lhs: Cell, rhs: Cell) -> Bool {
        return lhs.key == rhs.key && lhs.text == rhs.text
    }

    var body: some View {
        HStack {
            Text(key)
            Text(" / ")
            Text(text)
            Button(action: confirm) {
                Label("", systemImage: "checkmark")
            }
        }
    }

    private func confirm() {
        // contrived example
        // this does not work
        data.updateValue(key: "C", value: "updated123")
    }

}
// data class (Data)

import SwiftUI

class Data: ObservableObject {

    @Published var data: [String:String]

    init() {
        data = [:]
    }

    func updateValue(key: String, value: String) {
        data[key] = value
        objectWillChange.send()
    }

}

答案1

得分: 0

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

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

struct Cell: View, Hashable, Identifiable {
    private let key: String
    @ObservedObject private var data: DataModel

    var text: String { data.data[key] ?? "" }

    init(key: String, data: DataModel, text: String) {
        self.key = key
        data.data[key] = text
        self.data = data
    }

    var body: some View {
        HStack {
            Text(key)
            Text(" / ")
            Text(text)
            Button(action: { confirm(key) }) {
                Label("", systemImage: "checkmark")
            }
        }
    }

    private func confirm(_ key: String) {
        data.updateValue(key: key, value: "\(Int.random(in: 1...100))")
    }

    var id: String { "\(key)" }
    func hash(into hasher: inout Hasher) { hasher.combine(key) }
    static func ==(lhs: Cell, rhs: Cell) -> Bool { lhs.key == rhs.key }
}

请注意,我将 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.

struct Cell: View, Hashable, Identifiable {
    @State private var key = ""
    @ObservedObject private var data: DataModel

    var text: String { data.data[key] ?? "" }

    init(key: String, data: DataModel, text: String) {
        self.key = key
        data.data[key] = text
        self.data = data
    }

    var body: some View {
        HStack {
            Text(key)
            Text(" / ")
            Text(text)
            Button(action: { confirm(key) }) {
                Label("", systemImage: "checkmark")
            }
        }
    }

    private func confirm(_ key: String) {
        data.updateValue(key: key, value: "\(Int.random(in: 1...100))")
    }

    var id: String { "\(key)" }
    func hash(into hasher: inout Hasher) { hasher.combine(key) }
    static func ==(lhs: Cell, rhs: Cell) -> Bool { lhs.key == rhs.key }
}

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:

确定