I have a problem when I enter a character within a TextField (Within ExerciseSetView), I have to re-click the text box to make it so I can enter another character. If I remove the bindings from the Textfield I can enter text fluidly.
I think it has something to do with my presenter class and the updateSet function recreating a set instance because I have to replace some values two levels deep within an array.
//
// ContentView.swift
// test
//
//
import SwiftUI
import Combine
import CoreData
class WorkoutExerciseSetVM: Hashable, ObservableObject {
@Published public var id: Int
@Published public var reps: String
@Published public var weight: String
init(id: Int, reps: String, weight: String) {
self.id = id
self.reps = reps
self.weight = weight
}
static func ==(lhs: WorkoutExerciseSetVM, rhs: WorkoutExerciseSetVM) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
func hash(into hasher: inout Hasher) { return hasher.combine(ObjectIdentifier(self)) }
}
class WorkoutExerciseVM: Hashable, ObservableObject {
@Published public var id: UUID
@Published public var name: String
@Published public var sets: [WorkoutExerciseSetVM]
init(id: UUID, name: String, sets: [WorkoutExerciseSetVM]) {
self.id = id
self.name = name
self.sets = sets
}
static func ==(lhs: WorkoutExerciseVM, rhs: WorkoutExerciseVM) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
func hash(into hasher: inout Hasher) { return hasher.combine(ObjectIdentifier(self)) }
}
class WorkoutVM: Hashable, ObservableObject {
@Published public var id = UUID()
@Published public var name: String
@Published public var exercises: [WorkoutExerciseVM]
@Published public var started: Date? = Date()
@Published public var completed: Date? = Date()
init(id: UUID, name: String, exercises: [WorkoutExerciseVM], started: Date?, completed: Date?) {
self.id = id
self.name = name
self.exercises = exercises
self.started = started
self.completed = completed
}
static func ==(lhs: WorkoutVM, rhs: WorkoutVM) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
func hash(into hasher: inout Hasher) { return hasher.combine(ObjectIdentifier(self)) }
}
class WorkoutPresenter: ObservableObject {
@Published public var id: UUID
@Published public var exercises: [WorkoutExerciseVM]
@Published public var name: String
@Published public var started: Date?
@Published public var completed: Date?
init(routine: WorkoutVM) {
self.id = UUID()
self.name = routine.name
self.started = Date()
self.completed = nil
self.exercises = routine.exercises.map{ exercise in
return WorkoutExerciseVM(
id: UUID(),
name: exercise.name,
sets: [
WorkoutExerciseSetVM(id: 1, reps: "0", weight: "0")
]
)
}
}
func removeExercise(id: UUID) {
let exerciseId = id.uuidString;
self.exercises = self.exercises.filter{$0.id.uuidString != exerciseId}
}
func addSet(id: UUID) {
let exerciseId = id.uuidString;
self.exercises = self.exercises.map {
if ($0.id.uuidString == exerciseId) {
if ($0.sets.count == 0) {
$0.sets.append(WorkoutExerciseSetVM(id: 1, reps: "0", weight: "0"))
}
if let lastSet = $0.sets.last {
$0.sets.append(WorkoutExerciseSetVM(id: lastSet.id + 1, reps: lastSet.reps, weight: lastSet.weight))
}
}
return $0
}
}
func updateSet(id: UUID, set: WorkoutExerciseSetVM) {
let exerciseId = id.uuidString
self.exercises = self.exercises.map{
if $0.id.uuidString == exerciseId {
$0.sets = $0.sets.map{(oldExerciseSet) -> WorkoutExerciseSetVM in
if oldExerciseSet.id == set.id {
return set
}
return oldExerciseSet
}
return $0
}
return $0;
}
}
func removeSet(id: UUID) {
let exerciseId = id.uuidString;
self.exercises = self.exercises.map{(exercise) -> WorkoutExerciseVM in
if exercise.id.uuidString == exerciseId {
let newExercise = exercise
if newExercise.sets.count > 1 {
newExercise.sets.removeLast()
}
return newExercise
}
return exercise;
}
}
}
struct ContentView: View {
var body: some View {
VStack {
WorkoutView(presenter: WorkoutPresenter(routine: WorkoutVM(id: UUID(), name: "Test", exercises: [WorkoutExerciseVM(id: UUID(), name: "Exercise", sets: [WorkoutExerciseSetVM(id: 1, reps: "0", weight: "0")])], started: nil, completed: nil)))
}
}
}
struct WorkoutView: View {
@ObservedObject var presenter: WorkoutPresenter
var body: some View {
return GeometryReader { geo in
ZStack {
VStack {
ScrollView {
ForEach(self.presenter.exercises, id: \.self) { exercise in
ExerciseView(presenter: self.presenter, exercise: exercise)
}
}
}
}
}
}
}
struct ExerciseView: View {
@ObservedObject var presenter: WorkoutPresenter
var exercise: WorkoutExerciseVM
var body: some View {
VStack {
VStack(alignment: .leading) {
VStack {
VStack {
ForEach(exercise.sets, id: \.self) { exerciseSet in
ExerciseSetView(
set: exerciseSet,
onUpdate: { newExerciseSet in
self.presenter.updateSet(id: self.exercise.id, set: newExerciseSet)
}
)
}
}
}
}
HStack {
Button(action: {
self.presenter.addSet(id: self.exercise.id)
}) {
HStack {
Image(systemName: "plus")
Text("Add Set")
}
}
Button(action: {
self.presenter.removeSet(id: self.exercise.id)
}) {
HStack {
Image(systemName: "minus")
Text("Remove Set")
}
}
}
}
}
}
struct ExerciseSetView: View {
var set: WorkoutExerciseSetVM
var onUpdate: (_ set: WorkoutExerciseSetVM) -> Void
var body: some View {
let repBinding = Binding(
get: {
String(self.set.reps)
},
set: {
if ($0 as String?) != nil {
self.onUpdate(WorkoutExerciseSetVM(id: self.set.id, reps: $0 , weight: self.set.weight))
}
}
)
let weightBinding = Binding(
get: {
String(self.set.weight)
},
set: {
if ($0 as String?) != nil {
self.onUpdate(WorkoutExerciseSetVM(id: self.set.id, reps: self.set.reps, weight: $0 ))
}
}
)
return HStack {
Spacer()
// textfield that isn't working
TextField("", text: repBinding)
Spacer()
// textfield that isn't working
TextField("", text: weightBinding)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Your code has some fundamental errors. Please research about ObservableObject
and Published
values before going into production with this code. Otherwise, it would be quite hard to deal with this code later.
I have updated your views and that seems to work. You are not using ObservableObject
as they should be used. Just pass ObservableObjects
s around let them do the bindings for you instead of setting custom bindings.
struct ExerciseView: View {
@ObservedObject var presenter: WorkoutPresenter
@ObservedObject var exercise: WorkoutExerciseVM
var body: some View {
VStack {
VStack(alignment: .leading) {
ForEach(exercise.sets, id: \.self) { exerciseSet in
ExerciseSetView(set: exerciseSet)
}
}
HStack {
Button(action: {
self.presenter.addSet(id: self.exercise.id)
}) {
HStack {
Image(systemName: "plus")
Text("Add Set")
}
}
Button(action: {
self.presenter.removeSet(id: self.exercise.id)
}) {
HStack {
Image(systemName: "minus")
Text("Remove Set")
}
}
}
}
}
}
struct ExerciseSetView: View {
@ObservedObject var set: WorkoutExerciseSetVM
var body: some View {
HStack {
Spacer()
TextField("", text: $set.reps)
Spacer()
TextField("", text: $set.weight)
}
}
}
Let me know if this works for you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With