带关联属性的Swift枚举在SwiftUI View中使用的编译错误修复咨询
问题
拥有带关联属性的枚举CategoryType,想要在SwiftUI视图中列出所有case,但因关联属性无法使用CaseIterable和Identifiable协议,尝试通过计算属性allCases实现时出现编译错误:
Generic struct 'Picker' requires that 'CategoryType' conform to 'Hashable' Referencing initializer 'init(_:content:)' on 'ForEach' requires that 'CategoryType' conform to 'Identifiable'
枚举代码:
enum CategoryType: Decodable, Equatable { case psDaily, psWeekly, psMonthly case unknown(value: String) init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let status = try? container.decode(String.self) switch status { case "Daily": self = .psDaily case "Weekly": self = .psWeekly case "Monthly": self = .psMonthly default: self = .unknown(value: status ?? "unknown") } } var allCases: [CategoryType] { return [.psDaily, .psWeekly, .psMonthly] } var rawValue: String { switch self { case .psDaily: return "Daily" case .psWeekly: return "Weekly" case .psMonthly: return "Monthly" case .unknown: return "Unknown" } } }
视图代码:
import SwiftUI struct CategoryPicker: View { @Binding var selection: CategoryType var body: some View { NavigationStack { Form { Section { Picker("Category", selection: $selection) { ForEach(CategoryType().allCases) { category in CategoryView(category: category) .tag(category) } } } } } } } struct CategoryPicker_Previews: PreviewProvider { static var previews: some View { CategoryPicker(selection: .constant(.psDaily)) } }
解决方案
针对两个编译错误,分步骤修复:
1. 让CategoryType遵循Hashable协议
SwiftUI的Picker要求绑定的selection类型必须遵循Hashable,用于跟踪选中状态。由于Hashable继承自Equatable,且枚举的关联值String本身遵循Hashable,只需在枚举声明中添加Hashable即可自动合成实现:
enum CategoryType: Decodable, Equatable, Hashable { // 添加Hashable // 原有代码不变 }
2. 修复ForEach的标识问题
ForEach需要每个元素有唯一标识,有两种可选方案:
方案A:显式指定id参数
无需让枚举遵循Identifiable,直接在ForEach中通过id: \.self(因已遵循Hashable)或id: \.rawValue指定唯一标识,同时将allCases改为静态属性(无需创建枚举实例即可访问):
修改枚举的allCases:
static var allCases: [CategoryType] { // 改为static return [.psDaily, .psWeekly, .psMonthly] }
修改视图中的ForEach:
ForEach(CategoryType.allCases, id: \.self) { category in CategoryView(category: category) .tag(category) }
方案B:让CategoryType遵循Identifiable
通过扩展为枚举添加id属性,利用已有的rawValue作为唯一标识:
extension CategoryType: Identifiable { var id: String { rawValue } }
此时ForEach可简化为:
ForEach(CategoryType.allCases) { category in CategoryView(category: category) .tag(category) }
额外优化
建议修改rawValue中.unknown的返回值,返回实际关联的value而非固定字符串,更符合逻辑:
var rawValue: String { switch self { case .psDaily: return "Daily" case .psWeekly: return "Weekly" case .psMonthly: return "Monthly" case .unknown(let value): return value // 返回实际关联值 } }
完整修复后代码
枚举代码
enum CategoryType: Decodable, Equatable, Hashable { case psDaily, psWeekly, psMonthly case unknown(value: String) init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let status = try? container.decode(String.self) switch status { case "Daily": self = .psDaily case "Weekly": self = .psWeekly case "Monthly": self = .psMonthly default: self = .unknown(value: status ?? "unknown") } } static var allCases: [CategoryType] { return [.psDaily, .psWeekly, .psMonthly] } var rawValue: String { switch self { case .psDaily: return "Daily" case .psWeekly: return "Weekly" case .psMonthly: return "Monthly" case .unknown(let value): return value } } } // 可选:遵循Identifiable extension CategoryType: Identifiable { var id: String { rawValue } }
视图代码
import SwiftUI struct CategoryPicker: View { @Binding var selection: CategoryType var body: some View { NavigationStack { Form { Section { Picker("Category", selection: $selection) { // 若遵循了Identifiable,可省略id参数 ForEach(CategoryType.allCases) { category in CategoryView(category: category) .tag(category) } } } } } } } struct CategoryPicker_Previews: PreviewProvider { static var previews: some View { CategoryPicker(selection: .constant(.psDaily)) } }
内容的提问来源于stack exchange,提问作者LDropl
相关产品推荐
相关产品推荐

