I have Enum CategoryType
with associated properties and I would like to use it in my view just to list all the cases from enum.
I can's use CaseIterable
and Identifiable
for my enum with associated properties, that's why it's a bit tricky.
I tried to use computed property allCases
to list all the cases but it's still not compiling.
I'm getting these errors:
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"
}
}
}
here is my view:
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))
}
}
How to fix these issues or is there another way how to implement it ?
CodePudding user response:
First of all do as the compiler says and have the enum conform to Hashable
and Identifiable
extension CategoryType: Hashable, Identifiable {
var id: String { self.rawValue }
}
Then the allCases
property has nothing to do with self
so change it to static
static let allCases: [CategoryType] = {
return [.psDaily, .psWeekly, .psMonthly]
}()
And then change the ForEach
loop to use this property
ForEach(CategoryType.allCases) { category in
CodePudding user response:
enum
s are great but sometimes you need a struct
. When there might be unknowns is a perfect example because you can dynamically create objects.
struct CategoryType: Codable, Equatable, CaseIterable, RawRepresentable, Identifiable, Hashable {
//Conform to Identifiable using the rawValue
var id: String{
rawValue
}
//Keep the "String" for decoding and encoding
var rawValue: String
static let psDaily: CategoryType = .init(rawValue: "Daily")
static let psWeekly: CategoryType = .init(rawValue: "Weekly")
static let psMonthly: CategoryType = .init(rawValue: "Monthly")
init(rawValue: String) {
self.rawValue = rawValue
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
//Dont ignore errors
let status = try container.decode(String.self)
switch status {
case "Daily": self = .psDaily
case "Weekly": self = .psWeekly
case "Monthly": self = .psMonthly
//Create a custom `CategoryType` when the case is unknown.<---
default:
self.rawValue = status
}
}
//CaseIterable conformance
static var allCases: [CategoryType] = [.psDaily, .psWeekly, .psMonthly]
}
Then you can use it in a View
very similarly to the enum
but with the advantage of supporting the unknown.
struct CategoryPicker: View {
@Binding var selection: CategoryType
@State var string: String = ""
@State var cases: [CategoryType] = CategoryType.allCases
var body: some View {
NavigationStack {
Form {
Text(string)
.onAppear(){
encode()
}
Section {
Picker("Category", selection: $selection) {
//The loop now works as expected and has individual objects for the "unknown"
ForEach(cases) { category in
Text(category.rawValue)
.tag(category)
}
}
}.onAppear(){
decode()
}
}
}
}
//Sample encoding to demonstrate
func encode() {
let encoder: JSONEncoder = .init()
encoder.outputFormatting = .prettyPrinted
do{
let data = try encoder.encode(CategoryType.allCases)
string = String(data: data, encoding: .utf8) ?? "failed"
}catch{
print(error)
}
}
//Sample decoding that inclues "unknown" cases and creates object
func decode() {
let json = """
["Daily", "Weekly", "Monthly", "Unknown", "Something Unique"]
""".data(using: .utf8)!
let decoder: JSONDecoder = .init()
do{
cases = (try decoder.decode([CategoryType].self, from: json))
}catch{
print(error)
}
}
}