SwiftUI中MemberViewModel对象自比较始终返回False问题排查
SwiftUI选中状态失效问题排查与解决
问题场景
实现了遵循Equatable协议的MemberViewModel,使用UUID做对象比较;SwiftUI视图中点击圆形图标需显示黑色描边标记选中,但调试发现isSelected始终为false,选中状态无法生效。
相关代码
MemberViewModel实现
class MemberViewModel { private static var entities: [MemberViewModel] = [] private var entity: Member let id = UUID() init(_ entity: Member) { self.entity = entity } static func members() -> [MemberViewModel] { entities.removeAll() try? fetch().forEach { member in entities.append(MemberViewModel(member)) } return entities } } extension MemberViewModel: Equatable { static func == (lhs: MemberViewModel, rhs: MemberViewModel) -> Bool { return lhs.id == rhs.id } }
SwiftUI视图代码
struct MyView: View { @State var selectedMember: MemberViewModel? = nil var body: some View { let members = MemberViewModel.members() ScrollView(.horizontal, showsIndicators: true) { HStack(alignment: .top, spacing: 4) { ForEach (members, id: \.id) { member in var isSelected: Bool = selectedMember == member Circle() .fill(Color(.red)) .frame(width: 48, height: 48) .overlay() { if isSelected { Circle() .stroke(Color(.black), lineWidth: 2) } } .onTapGesture { selectedMember = member } } } } } }
尝试过的Binding方式:
let isSelected = Binding<Bool>(get: { self.selectedMember == member }, set: { _ in })
环境:Xcode 14.0.1、Swift 5.7
核心问题原因
- ViewModel的UUID随机生成导致实例不匹配:
MemberViewModel的id在初始化时生成新的UUID,每次调用members()方法都会重新创建所有ViewModel实例,新实例的UUID与之前选中的旧实例UUID完全不同,即使对应同一个Member实体,Equatable比较也会返回false。 - body中重复调用members():SwiftUI的
body会频繁触发重渲染,每次都会调用MemberViewModel.members()生成全新的ViewModel数组,导致选中的实例永远和当前渲染的实例不是同一批,状态判断失效。
解决方案
1. 绑定ViewModel的id到实体唯一标识
将ViewModel的id改为使用Member实体自身的唯一标识(比如member.id,需确保Member有稳定的唯一字段),避免随机UUID带来的实例不匹配:
class MemberViewModel { private static var entities: [MemberViewModel] = [] private var entity: Member // 使用Member自身的唯一ID,而非随机生成 let id: UUID // 类型根据Member的实际ID字段调整 init(_ entity: Member) { self.entity = entity self.id = entity.id // 替换为Member的真实唯一字段 } static func members() -> [MemberViewModel] { entities.removeAll() try? fetch().forEach { member in entities.append(MemberViewModel(member)) } return entities } } extension MemberViewModel: Equatable { static func == (lhs: MemberViewModel, rhs: MemberViewModel) -> Bool { return lhs.id == rhs.id } }
2. 缓存members数组避免重复创建
在视图中缓存members数组,仅在必要时加载,避免每次重渲染都生成新实例:
struct MyView: View { @State var selectedMember: MemberViewModel? = nil @State private var members: [MemberViewModel] = [] var body: some View { ScrollView(.horizontal, showsIndicators: true) { HStack(alignment: .top, spacing: 4) { ForEach (members, id: \.id) { member in // 直接通过id判断选中状态,更直观 let isSelected = selectedMember?.id == member.id Circle() .fill(Color(.red)) .frame(width: 48, height: 48) .overlay { if isSelected { Circle() .stroke(Color(.black), lineWidth: 2) } } .onTapGesture { selectedMember = member } } } } .onAppear { // 视图首次出现时加载数据 members = MemberViewModel.members() } } }
3. 优化ViewModel的缓存逻辑
修改MemberViewModel的静态数组逻辑,避免每次调用members()都清空重建,提升性能同时保证实例一致性:
class MemberViewModel { private static var entities: [MemberViewModel]? private var entity: Member let id: UUID init(_ entity: Member) { self.entity = entity self.id = entity.id } static func members() -> [MemberViewModel] { // 已有缓存直接返回,避免重复创建 if let cachedEntities = entities { return cachedEntities } guard let fetchedMembers = try? fetch() else { return [] } let viewModels = fetchedMembers.map(MemberViewModel.init) entities = viewModels return viewModels } // 提供刷新方法,需要更新数据时调用 static func refreshMembers() { entities = nil } }
内容的提问来源于stack exchange,提问作者dohpaz42
相关产品推荐
相关产品推荐

