Been away from the swift-ing for a good 3 years now. Getting back into it now and trying to learn Combine and SwiftUI.
Making a test Workout app. Add an exercise, record reps and weights for 3 sets. Save data.
I'm having issues moving some data around from views to data store. I think I'm confusing all the different property wrappers. Summary at the bottom after code.
App:
@main
struct TestApp: App {
@StateObject private var store = ExerciseStore()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(store)
}
}
}
Views:
struct ContentView: View {
@EnvironmentObject var store: ExerciseStore
var body: some View {
List {
ForEach($store.completedExercises) { $exercise in
ExerciseView(exercise: $exercise)
}
}
}
}
struct ExerciseView: View {
@Binding var exercise: CompletedExercise
var body: some View {
VStack {
Text(exercise.exercise.name)
SetView(set: $exercise.sets[0])
SetView(set: $exercise.sets[1])
SetView(set: $exercise.sets[2])
}
}
}
struct SetView: View {
@Binding var set: ExerciseSet
var body: some View {
HStack {
TextField(
"Reps",
value: $set.reps,
formatter: NumberFormatter()
)
TextField(
"Weight",
value: $set.weight,
formatter: NumberFormatter()
)
}
}
}
Store:
class ExerciseStore: ObservableObject {
@Published var completedExercises: [CompletedExercise] = [CompletedExercise(Exercise())]
init() {
if let data = UserDefaults.standard.data(forKey: "CompletedExercise") {
if let decoded = try? JSONDecoder().decode([CompletedExercise].self, from: data) {
completedExercises = decoded
return
}
}
}
func save() {
if let encoded = try? JSONEncoder().encode(completedExercises) {
UserDefaults.standard.set(encoded, forKey: "CompletedExercise")
}
}
}
Models:
class CompletedExercise: Codable, Identifiable, ObservableObject {
var id = UUID().uuidString
var exercise: Exercise
@Published var sets = [
ExerciseSet(),
ExerciseSet(),
ExerciseSet()
]
init(exercise: Exercise) {
self.exercise = exercise
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
exercise = try container.decode(Exercise.self, forKey: .exercise)
sets = try container.decode([ExerciseSet].self, forKey: .sets)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(exercise, forKey: .exercise)
try container.encode(sets, forKey: .sets)
}
}
private enum CodingKeys: CodingKey {
case id, exercise, sets
}
struct Exercise: Codable, Identifiable {
var id = -1
var name = "Bench Press"
}
class ExerciseSet: Codable, ObservableObject {
@Published var reps: Int?
@Published var weight: Int?
init() {}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
reps = try container.decodeIfPresent(Int.self, forKey: .reps)
weight = try container.decodeIfPresent(Int.self, forKey: .weight)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(reps, forKey: .reps)
try container.encode(weight, forKey: .weight)
}
}
private enum CodingKeys: CodingKey {
case reps, weight
}
Thats more or less the current code.
I've added a bunch of print statements in the save function in ExerciseStore
to see what gets saved.
No matter what I've tried, I can't get the reps/weight via the SetView
text fields to persist through the ExerciseStore
and get saved.
I've played around with @Binding and such as well but can't get it working.
What am I missing/messing up with the new SwiftUI data flows.