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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 12:05:35