I want to change rate parameter and display in 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: { $0.fundCode == fund.fundCode }) {
viewModel.funds[index].rate = 5
}
}
}
}
}
}
}
}
If I use Struct for model, view did updated as expected.
// 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)
...
}
}
If I use Class for model, view did not updated.
// 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)
...
}
}
I want to use Class for model because it needs to be inherit some properties from some Super Class.
What is the difference between Struct model and Class model for SwiftUI perspective and why view did not update when I use Class model over Struct model?
Note: FundModel conforms Equtable and Hashable somewhere else.
CodePudding user response:
try this approach using class ViewModel: ObservableObject {...}
(without the @MainActor
) and viewModel.objectWillChange.send()
as in this example code:
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: { $0.fundCode == fund.fundCode }) {
viewModel.objectWillChange.send() // <-- here
viewModel.funds[index].rate = 5
}
}
}
}
}
}
}
}
final class FundModel: Decodable {
let fundCode: String
var rate: Int = 0
//....
}
CodePudding user response:
The view updates when the @Published var
changes. Your @Published var
is an array of FundModel
.
A struct is not a mutable entity, so when you use a struct
, the array actually replaces the object with a new one. The system recognises the change in the array and publishes it to the view.
A class is mutable, so when you change the rate inside the class, you still have the same instance. So, the array of FundModel
does not change: it still contains exactly the same elements inside, even though one of the elements have changed an internal variable.
The @Published
wrapper cannot detect that an internal variable of the members of the array has changed: it detects only when the array has different elements inside.
Conclusion: struct
needs to create a new element to change, @Published
recognises this. class
can change, so for @Published
the object is still the same (the array didn't change).