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

SwiftUI可复用Picker组件协议无法遵循Hashable问题求助

解决方案

问题出在any Pickable作为存在类型(existential type),虽然Pickable协议继承自Hashable,但存在类型本身无法自动遵循Hashable协议,导致ForEach和Picker的selection绑定报错。以下是两种无需指定具体类型即可实现的方案:

方案一:泛型实现(推荐)

将PickerRow改为泛型结构体,利用Swift的类型推断自动适配不同Pickable类型,同时保证Hashable一致性:

import SwiftUI

// 泛型版本的PickerRow,约束T遵循Pickable
struct PickerRow<T: Pickable>: View {
    let title: String
    let options: [T]
    @Binding var selection: T // 使用Binding而非State,更符合组件复用的数据流逻辑
    
    var body: some View {
        HStack {
            Spacer()
            Text(title)
                .font(.subHeading)
            Picker(title, selection: $selection) {
                ForEach(options, id: \.self) { option in
                    Text(option.name)
                }
            }
            .pickerStyle(.menu)
        }
    }
}

protocol Pickable: Hashable {
    var name: String { get }
}

使用示例

// 定义遵循Pickable的具体类型
enum Gender: String, Pickable {
    case male = "男"
    case female = "女"
    
    var name: String { rawValue }
}

struct ContentView: View {
    @State private var selectedGender = Gender.male
    
    var body: some View {
        // 编译器自动推断T为Gender,无需手动指定
        PickerRow(title: "性别", options: [.male, .female], selection: $selectedGender)
    }
}

方案二:类型擦除包装器

如果需要保持组件的非泛型特性,可通过类型擦除创建AnyPickable包装器,手动实现Hashable协议:

import SwiftUI

// 类型擦除的Pickable包装器
struct AnyPickable: Hashable {
    private let base: any Pickable
    private let hashValue: Int
    private let isEqual: (AnyPickable) -> Bool
    
    init(_ base: any Pickable) {
        self.base = base
        self.hashValue = base.hashValue
        self.isEqual = { other in
            guard let otherBase = other.base as? type(of: self.base) else {
                return false
            }
            return self.base == otherBase
        }
    }
    
    var name: String { base.name }
    
    static func == (lhs: AnyPickable, rhs: AnyPickable) -> Bool {
        lhs.isEqual(rhs)
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(hashValue)
    }
}

// 扩展Pickable,快速转成AnyPickable
extension Pickable {
    func eraseToAnyPickable() -> AnyPickable {
        AnyPickable(self)
    }
}

// 非泛型的PickerRow
struct PickerRow: View {
    let title: String
    let options: [AnyPickable]
    @Binding var selection: AnyPickable
    
    var body: some View {
        HStack {
            Spacer()
            Text(title)
                .font(.subHeading)
            Picker(title, selection: $selection) {
                ForEach(options, id: \.self) { option in
                    Text(option.name)
                }
            }
            .pickerStyle(.menu)
        }
    }
}

protocol Pickable: Hashable {
    var name: String { get }
}

使用示例

enum Gender: String, Pickable {
    case male = "男"
    case female = "女"
    
    var name: String { rawValue }
}

struct ContentView: View {
    @State private var selectedGender = Gender.male.eraseToAnyPickable()
    
    var body: some View {
        PickerRow(
            title: "性别",
            options: [.male, .female].map { $0.eraseToAnyPickable() },
            selection: $selectedGender
        )
    }
}

内容的提问来源于stack exchange,提问作者Steve Schwedt

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 06:31:12