使用 ForEach 更改数据时,视图不会更新

View is not updated when the data is changed with ForEach

我想更改速率参数并在 ForEach 中显示。

// ViewModel.swift

@MainActor final class ViewModel: ObservableObject {
    let serviceContainer: ServiceContainerProtocol

    @Published var funds: [FundModel] = []
    ...

    init(serviceContainer: ServiceContainerProtocol) {
        self.serviceContainer = serviceContainer

        Task { await getFunds() }
        ...
    }

    ...
}
// FundView.swift

struct FundView: View {
    @StateObject private var viewModel: ViewModel

    init(serviceContainer: ServiceContainerProtocol) {
        self._viewModel = StateObject(wrappedValue: ViewModel(serviceContainer: serviceContainer))
    }

    var body: some View {
        ScrollView { 
            VStack {
                ForEach(viewModel.funds, id: \.fundCode) { fund in
                    VStack {
                        Text(String(fund.rate))

                        Button("Add 5") {
                            if let index = viewModel.funds.firstIndex(where: { [=11=].fundCode == fund.fundCode }) {
                                viewModel.funds[index].rate += 5
                            }
                        }
                    }
                }
            }
        }
    }
}

如果我将 Struct 用于模型,视图会按预期更新。

// FundModel.swift

struct FundModel: Decodable {
    let fundCode: String
    ...

    // Internal - Not related to api.
    var rate: Int = 0
    ...

    // MARK: CodingKeys

    private enum CodingKeys: String, CodingKey {
        case fundCode
        ...
    }

    // MARK: Decodable

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        fundCode = try container.decode(String.self, forKey: .fundCode)
        ...
    }
}

如果我使用 Class 作为模型,视图不会更新。

// FundModel.swift

final class FundModel: Decodable {
    let fundCode: String
    ...

    // Internal - Not related to api.
    var rate: Int = 0
    ...

    // MARK: CodingKeys

    private enum CodingKeys: String, CodingKey {
        case fundCode
        ...
    }

    // MARK: Decodable

    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        fundCode = try container.decode(String.self, forKey: .fundCode)
        ...
    }
}

我想使用 Class 作为模型,因为它需要从某些 Super Class 继承一些属性。

对于 SwiftUI 透视图,Struct 模型和 Class 模型有什么区别?为什么当我在 Struct 模型上使用 Class 模型时视图没有更新?

注意:FundModel 在其他地方符合 Equtable 和 Hashable。

视图会在 @Published var 更改时更新。您的 @Published varFundModel.

的数组

结构不是可变实体,因此当您使用 struct 时,数组实际上用新对象替换了对象。系统识别数组中的变化并将其发布到视图。

A class 是可变的,因此当您更改 class 内的速率时,您仍然拥有 相同的 实例。因此,FundModel 的数组没有改变:它仍然包含完全相同的内部元素,即使其中一个元素已经更改了内部变量。

@Published 包装器无法检测到数组成员的内部变量已更改:它仅在数组内部有不同元素时检测到。

结论:struct需要创建一个新的元素来改变,@Published承认这一点。 class 可以改变,所以对于 @Published 对象仍然是相同的(数组没有改变)。

使用 class ViewModel: ObservableObject {...}(没有 @MainActor)和 viewModel.objectWillChange.send() 尝试这种方法 如本示例代码所示:

 struct FundView: View {
    @StateObject private var viewModel: ViewModel

    init(serviceContainer: ServiceContainerProtocol) {
        self._viewModel = StateObject(wrappedValue: ViewModel(serviceContainer: serviceContainer))
    }

    var body: some View {
        ScrollView {
            VStack {
                ForEach(viewModel.funds, id: \.fundCode) { fund in
                    VStack {
                        Text(String(fund.rate))

                        Button("Add 5") {
                            if let index = viewModel.funds.firstIndex(where: { [=10=].fundCode == fund.fundCode }) {
                                viewModel.objectWillChange.send() // <-- here
                                viewModel.funds[index].rate += 5
                            }
                        }
                    }
                }
            }
        }
    }
}

final class FundModel: Decodable {
    let fundCode: String
    var rate: Int = 0

   //....
}