SwiftUI中自定义协议遵循Identifiable后无法正常使用的问题
问题原因
问题出在any CustomProtocol是存在类型,SwiftUI的ForEach(不带id参数的版本)和sheet(item:)要求的是符合Identifiable的具体类型,存在类型的Identifiable实现没法被SwiftUI的泛型系统正确识别。另外,sheet(item:)还要求类型符合Equatable(用来判断要不要显示/隐藏弹窗),但any CustomProtocol默认没有自动生成Equatable的实现。
解决方案一:让协议支持Equatable并手动实现存在类型的比较
直接修改协议让它继承Equatable,然后给any CustomProtocol写一个基于id的比较逻辑(因为id是唯一的,用它判断相等完全合理):
protocol CustomProtocol: Identifiable, Equatable { var id: String { get } } // 给存在类型实现Equatable比较 func == (lhs: any CustomProtocol, rhs: any CustomProtocol) -> Bool { lhs.id == rhs.id } struct CustomModel: CustomProtocol { let id = UUID().uuidString } // 后面的Store和View代码不用改,现在ForEach和sheet都能正常工作了 class CustomModelStore: ObservableObject { @Published var models: [any CustomProtocol] = [] init() { models = Array(repeating: CustomModel(), count: 10) } } struct CustomProtocolView: View { @StateObject var store = CustomModelStore() @State var selectedModel: (any CustomProtocol)? var body: some View { VStack { ForEach(store.models) { model in Text(model.id) .font(.footnote) .onTapGesture { selectedModel = model } } .sheet(item: $selectedModel) { model in Text(model.id) .font(.subheadline) } } } }
这种方案的核心就是手动实现存在类型的Equatable,满足sheet的要求,同时协议已经继承了Identifiable,ForEach也能正常识别。
解决方案二:用类型擦除包装器转换为具体类型
如果不想修改原协议的继承关系,可以写一个包装器,把any CustomProtocol转成符合Identifiable和Equatable的具体类型:
protocol CustomProtocol: Identifiable { var id: String { get } } struct CustomModel: CustomProtocol { let id = UUID().uuidString } // 类型擦除包装器 struct AnyIdentifiableCustomProtocol: Identifiable, Equatable { let id: String private let base: any CustomProtocol init(_ base: any CustomProtocol) { self.base = base self.id = base.id } static func == (lhs: AnyIdentifiableCustomProtocol, rhs: AnyIdentifiableCustomProtocol) -> Bool { lhs.id == rhs.id } } class CustomModelStore: ObservableObject { @Published var models: [AnyIdentifiableCustomProtocol] = [] init() { let rawModels = Array(repeating: CustomModel(), count: 10) models = rawModels.map(AnyIdentifiableCustomProtocol.init) } } struct CustomProtocolView: View { @StateObject var store = CustomModelStore() @State var selectedModel: AnyIdentifiableCustomProtocol? var body: some View { VStack { ForEach(store.models) { model in Text(model.id) .font(.footnote) .onTapGesture { selectedModel = model } } .sheet(item: $selectedModel) { model in Text(model.id) .font(.subheadline) } } } }
这个方案通过包装器把存在类型转成具体类型,同时实现了Equatable,完美适配SwiftUI的要求,而且不需要改动原协议。
补充说明
- SwiftUI的
sheet(item:)和fullScreenCover(item:)要求绑定的类型必须同时符合Identifiable和Equatable,因为需要通过Equatable判断值的变化来触发弹窗的显示或隐藏。 - 存在类型(
any Protocol)本身不会自动生成Equatable实现,必须手动定义或者用包装器转成具体类型。
内容的提问来源于stack exchange,提问作者Nizami
相关产品推荐
相关产品推荐

