使用 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 var
是 FundModel
.
的数组
结构不是可变实体,因此当您使用 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
//....
}
我想更改速率参数并在 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 var
是 FundModel
.
结构不是可变实体,因此当您使用 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
//....
}