SwiftUI 使用自定义对象属性更改更新视图

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

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) -&gt; Bool {
        return lhs.objectId == rhs.objectId
    } // &lt;- 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(&quot;Header&quot;)
            }.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(&quot;Header&quot;)
            }.padding(.horizontal)
        }
    }
}

huangapple
  • 本文由 发表于 2023年2月19日 19:25:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/75499789.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定