Running into an issue using a Picker against an enum. Trying to create a workout app, where each Day (type, 7) of the ExercisePlan needs assigned to a DayType (enum). Then I'd build out the ability to add Workouts (type).
I'm currently just going the lazy route and defining an array of Days in the view. The issue comes when I use a Picker. I can successfully make one change, but then immediately all of the Pickers stop working.
Day
struct Day: Identifiable, Codable, Hashable, Equatable {
var id: Int // 1 - 7
var type: DayType
var workout: [Workout]?
init(id: Int) {
self.id = id
self.type = .rest
self.workout = [Workout]()
}
static func ==(lhs: Day, rhs: Day) -> Bool {
return lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
enum CodingKeys: String, CodingKey {
case id
case type
case workout
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(Int.self, forKey: .id)
type = try values.decode(DayType.self, forKey: .type)
if values.contains(.workout) {
workout = try values.decode([Workout]?.self, forKey: .workout)
} else {
workout = nil
}
}
}
DayType
enum DayType: Hashable, Codable, CaseIterable, Identifiable, CustomStringConvertible {
case upper
case lower
case core
case lowerAndCore
case rest
var id: Self { self }
var description: String {
switch self {
case .upper:
return "Upper"
case .lower:
return "Lower"
case .core:
return "Core"
case .lowerAndCore:
return "Lower and Core"
case .rest:
return "Rest"
}
}
}
ViewCode, including Picker
import SwiftUI
struct ExercisePlanBuilderView: View {
@ObservedObject var model: MainViewModel
@State var selectedUser: User
@State var days = [Day(id: 1), Day(id: 2), Day(id: 3), Day(id: 4), Day(id: 5), Day(id: 6), Day(id: 7)]
var body: some View {
VStack {
HStack {
Button(action: {
withAnimation {
model.showExerciseBuilder = false
}
}, label: {
HStack {
Image(systemName: "arrow.left")
.foregroundStyle(.red)
Text("Discard")
.foregroundStyle(.red)
.bold()
}
})
Spacer()
Text("Exercise Plan Builder")
.bold()
}
.padding(.horizontal)
ScrollView {
ForEach($days, id: \.id) { day in
HStack {
Text("Day " + day.id.description + " - ")
.font(.title)
Picker("Select a day focus", selection: day.type) {
ForEach(DayType.allCases, id: \.self) {
Text($0.description).tag($0)
}
}
.pickerStyle(.segmented)
}
}
}
}
}
}
#Preview {
ExercisePlanBuilderView(model: MainViewModel(), selectedUser: User())
}
Clicking them still shows a list to choose from, but anything beyond that first selection doesn't take affect. I've tried a few different ways to write this with no luck.
Your
Equatable
andHashable
implementations only comparesid
:This means that after selecting a different
DayType
with the picker, nothing changes, as far as SwiftUI can see. The@State
ofdays
is still equal to what it originally was. That causes SwiftUI to not update anything in the UI.You should also take
type
into account:Or use the automatically generated implementations if possible.
Note that equality and identity are different concepts. It's the difference between "something changed about this
Day
" and "thisDay
has magically disappeared and a newDay
appears".Identifiable
is already satisfied by theid
property, which I recommend declaring as alet
instead of avar
. You don't need theid:
parameter forForEach
- just doForEach($days) { day in ...
You shouldn't make the equality of your struct based solely on its identity. Properties of a
Day
can change (equality), without changing whatDay
it is (identity).