You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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


核心问题原因

  1. ViewModel的UUID随机生成导致实例不匹配:MemberViewModel的id在初始化时生成新的UUID,每次调用members()方法都会重新创建所有ViewModel实例,新实例的UUID与之前选中的旧实例UUID完全不同,即使对应同一个Member实体,Equatable比较也会返回false。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 17:51:46