英文:
SwiftUI update a view with custom object property change
问题
在将我的应用程序转换为SwiftUI的过程中,我遇到了一个问题。我知道根本原因是什么,但似乎无法找到解决方法。
我正在使用Parse实时查询来更新包含自定义对象数组的ViewModel。当从服务器添加/删除对象时,一切都按预期工作,UI也按预期更新。但是,当我尝试更新对象参数时,UI不反映更改,尽管对象在ViewModel中成功更新。
以下代码是我的代码的简化版本,但显示了该问题。
struct Candidate : ParseObject, Equatable {
static func == (lhs: ParseCandidate, rhs: ParseCandidate) -> Bool {
return lhs.objectId == rhs.objectId
} // <- 我认为这是导致问题的原因
var objectId : String?
var name : String?
var status : String?
}
class CandidatesViewModel : ObservableObject {
@Published candidatesList = [Candidates]()
init() {
//1. 从服务器获取对象
//2. 订阅实时查询
}
// 当从服务器接收到更新时,调用此方法
func updateCandiate(_ candidate : Candidate) {
if let index = candidatesList.firstIndex(where: {$0 == candidate}) {
candidatesList[index] = candidate
}
}
}
struct CandidateListView : View {
@StateObject var viewModel = CandidatesViewModel()
var body: some View {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 180,maximum: 180),spacing: 20) ], alignment: .leading, spacing: 30, pinnedViews: [.sectionHeaders]) {
Section {
ForEach(viewModel.candidatesList, id: \.self){ candidate in
CandidateCard(candidate: candidate)
}
}header: {
Text("Header")
}.padding(.horizontal)
}
}
}
struct CandidateCardView : View {
let candidate : Candidate
var body : some View {
VStack{
Text(candidate.name!)
Text(candidate.status!)
}
}
}
我注意到,如果我删除了Equatable
,Swift会将新实例添加到数组中,因为Swift认为它是一个新对象,因为所有属性都必须匹配。
到目前为止,唯一反映更新的方法是将CandidateCardView
内的候选人设置为@Binding
,但这似乎是不必要的并且复杂化了事情。
非常感谢任何指导。
英文:
I in the process of converting my application to SwiftUI but have encountered a problem. I know what the root cause is but can’t seem to figure out how to work around it.
I am using Parse live queries to update my ViewModel which contains an array of custom objects. Everything works as expected when adding/removing objects from the server the UI updates as expected however when I am trying to update an object parameter the UI doesn’t reflect the change although the object updates successfully in the ViewModel.
The code below is a simplified version of my code but shows the issue.
Hopefully someone can shed some light or point me to the right direction.
struct Candidate : ParseObject, Equatable {
static func == (lhs: ParseCandidate, rhs: ParseCandidate) -> Bool {
return lhs.objectId == rhs.objectId
} // <- I believe this is causing the issue
var objectId : String?
var name : String?
var status : String?
}
class CandidatesViewModel : ObservableObject {
@Published candidatesList = [Candidates]()
init() {
//1. get objects from server
//2. subscribe to live queries
}
// this method get called when an update is received from the server
func updateCandiate(_ candidate : Candidate) {
if let index = candidatesList.first(where: {$0 == candidate}) {
candidatesList[index] = candidate
}
}
}
struct CandidateListView : View {
@StateObject var viewModel = CandidatesViewModel()
var body: some View {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 180,maximum: 180),spacing: 20) ], alignment: .leading, spacing: 30, pinnedViews: [.sectionHeaders]) {
Section {
ForEach(viewModel.candidatesList, id: \.self){ candidate in
CandidateCard(candidate: candidate)
}
}header: {
Text("Header")
}.padding(.horizontal)
}
}
}
struct CandidateCardView : View {
let candidate : Candidate
var body : some View {
VStack{
Text(candidate.name!)
Text(candidate.status!)
}
}
}
I have noticed if I removed the equatable a new instance gets added to the array as swift assumes its a new object because all properties have to match.
the only way to reflect the update so far is if I make the candidate inside the CandidateCardView @Binding then the update is reflected but seems unnecessary and complicates things.
Any guidance is highly appreciated.
答案1
得分: 1
SwiftUI使用Equatable
来确定对象是否发生了更改。在您的情况下,您正在使用objectId
来确定相等性,因此当其他字段发生更改时,它不会被标记为已更新。要在其他属性更改时进行更新,请将其包含在==
中,或者只使用生成的版本,其中包含所有属性。您还应该使它成为Identifiable
。
在这种情况下,在updateCandiate
中,您需要更新为{$0.objectId == candidate.objectId}
。
现在Candidate
已经是Identifiable
。
英文:
SwiftUI uses Equatable
conformance to determine whether an object has changed. In your case, you're using the objectId
to determine equality, so it won't register as updated when an different field changes. To update when another property changes, include that in the ==
, or just use the synthesised version, which includes all properties. You should also make it Identifiable
struct Candidate : ParseObject, Equatable, Identifiable {
var id: String { objectId ?? UUID().uuidString }
var objectId : String? // Can this _really_ be nil?
var name : String?
var status : String?
}
In that case, in updateCandiate
you'll need to update to {$0.objectId == candidate.objectId}
class CandidatesViewModel : ObservableObject {
@Published candidatesList = [Candidates]()
// this method get called when an update is received from the server
func updateCandiate(_ candidate : Candidate) {
if let index = candidatesList.first(where: {$0.objectId == candidate.objectId}) {
candidatesList[index] = candidate
}
}
}
and now Candidate
is Identifiable
struct CandidateListView : View {
@StateObject var viewModel = CandidatesViewModel()
var body: some View {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 180,maximum: 180),spacing: 20) ], alignment: .leading, spacing: 30, pinnedViews: [.sectionHeaders]) {
Section {
ForEach(viewModel.candidatesList){ candidate in
CandidateCard(candidate: candidate)
}
}header: {
Text("Header")
}.padding(.horizontal)
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论