SwiftUI中自定义对象属性变更后视图未更新的解决求助
你的判断准确,Equatable的实现是问题核心。当使用ForEach(viewModel.candidatesList, id: \.self)时,SwiftUI依赖Equatable判断元素是否变化。你的==仅比较objectId,导致属性更新后的新旧Candidate被视为“相等”,SwiftUI因此认为元素未变化,不更新对应UI。
以下是几种合理的解决方法:
方案1:修改ForEach的标识为objectId(推荐)
无需改动Equatable实现,仅调整ForEach的id参数:
ForEach(viewModel.candidatesList, id: \.objectId){ candidate in CandidateCard(candidate: candidate) }
原理
objectId是每个候选对象的唯一标识,用它作为ForEach的跟踪ID后,SwiftUI会通过该值识别元素。当你替换数组中的Candidate实例时,即使objectId相同,SwiftUI会检测到传给CandidateCard的是新的struct实例,从而自动更新该卡片UI。同时你的Equatable实现仍能正常工作,避免数组出现重复对象。
方案2:将Candidate改为ObservableObject(适合复杂对象场景)
如果候选对象的属性更新频繁,可将Candidate从struct改为class并遵循ObservableObject:
class Candidate : ParseObject, Equatable, ObservableObject { static func == (lhs: Candidate, rhs: Candidate) -> Bool { return lhs.objectId == rhs.objectId } var objectId : String? @Published var name : String? @Published var status : String? }
同时修改CandidateCardView,用@ObservedObject接收对象:
struct CandidateCardView : View { @ObservedObject var candidate : Candidate var body : some View { VStack{ Text(candidate.name!) Text(candidate.status!) } } }
此时ViewModel的更新方法可直接修改属性,无需替换整个对象:
func updateCandiate(_ candidate : Candidate) { if let index = candidatesList.firstIndex(where: {$0 == candidate}) { candidatesList[index].name = candidate.name candidatesList[index].status = candidate.status } }
原理
@Published会在属性更新时发送通知,CandidateCardView作为观察者会自动刷新UI,无需依赖数组的替换操作,适合属性频繁变更的场景。
方案3:手动触发数组刷新(临时应急方案)
若不想改动现有结构,可在更新后手动强制@Published发送通知:
func updateCandiate(_ candidate : Candidate) { if let index = candidatesList.firstIndex(where: {$0 == candidate}) { candidatesList[index] = candidate // 手动触发数组刷新 candidatesList = candidatesList } }
说明
通过重新赋值数组,强制@Published触发变化通知,但会导致整个LazyVGrid重新渲染,性能不如前两种方案,仅作为临时解决手段。
内容的提问来源于stack exchange,提问作者OverD

