Say you have a SwiftData object that doesn't allow optionals:
@Model
class MySet: Identifiable {
var id: UUID
var weight: Int
var reps: Int
var isCompleted: Bool
var exercise: Exercise
Then you get from your MySet data a list of these MySets and append them into a State:
@State var sets: [MySet]
if let newSets = exercise.sets { //unwrapping the passed exercises sets
if (!newSets.isEmpty) { //we passed actual sets in so set them to our set
sets = newSets
}
And you use that State array in a ForEach and bind the Textfield directly to the data like so:
ForEach($sets){ $set in
TextField("\(set.weight)", value: $set.weight, formatter: NumberFormatter())
.keyboardType(.decimalPad)
TextField("\(set.reps)", value: $set.reps,formatter: NumberFormatter())
.keyboardType(.decimalPad)
}
This will bind whatever is written into the TextField directly to the SwiftData object which is a problem because say you have 50 written and you want to change it to 60 you need to be able to clear the 50 to write the 60 and since the SwiftData object doesn't allow nil it will allow you to remove the 0 but not the 5. Is there anyway to remedy this without having the swiftData object allow optionals for example catching the clearing of the TextField and making it 0 instead of nil when its cleared?
Post
Replies
Boosts
Views
Activity
I have a SwiftData object Set that has a non Optional reps and Weight and when my editView gets initialized I Set the existing sets to a State object:
//The user will temporarily write to this sets array than after he is done we will save it to exercises
@State var sets: [MySet]
.onAppear {
//this will check if the exercise has sets and if it does we will put them into State but if its doesnt we will create 2
// exercise = exercise
if let newSets = exercise.sets { //unwrapping the passed exercises sets
if (!newSets.isEmpty) { //we passed actual sets in so set them to our sets
//Copy over all of those Sets into new Sets
sets = newSets
}
else { //the exercise had no sets meaning an empty array was passed so we fill it with our mock Sets
sets = [MySet(id: UUID(), weight: 0, reps: 0,isCompleted: false, exercise: exercise), MySet(id: UUID(), weight: 0, reps: 0, isCompleted: false, exercise: exercise)]
}
sortSets()
}
//The optional exercise sets array was empty so load default (0) sets into State
else {
sets = [MySet(id: UUID(), weight: 0, reps: 0,isCompleted: false, exercise: exercise), MySet(id: UUID(), weight: 0, reps: 0, isCompleted: false, exercise: exercise)]
sortSets()
}
}
Then I iterate through this State of sets and bind the weight and reps to two TextFields:
ForEach($sets){ $set in
//HStack for Editing the Details of a Set
HStack(spacing: 10) {
Group {
if(set.isCompleted) {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.green) // Using the custom color
.font(.system(size: 22)) // Adjust the size here
} else {
Image(systemName: "circle")
.font(.system(size: 22)) // Adjust the size here
}
}
.onTapGesture {
if(set.reps != 0 && set.weight != 0) {
set.isCompleted.toggle()
}
}
//Enter Reps and Weight
HStack {
VStack(alignment: .leading){
Text("Kg")
.foregroundStyle(.gray)
.font(.subheadline)
// I am having problems with these TextFields and its when im editting an old reps or weight i cant delete the number
//already there to add a new one it wont let the reps of weight TextField Become empty
TextField("\(set.weight)", value: $set.weight, formatter:Formatter.valueFormatter)
.keyboardType(.decimalPad)
}
.frame(width: 45)
.padding(.leading)
//.background(.yellow)
//Enter Reps
VStack(alignment: .leading) {
Text("Reps")
.foregroundStyle(.gray)
.font(.subheadline)
TextField("\(set.reps)", value: $set.reps,formatter:Formatter.valueFormatter)
.keyboardType(.decimalPad)
}
.frame(width: 45)
//.background(.blue)
}
Spacer()
//Button to dublicate the current set and add it to the list
//even this dupilicate button will allow me to make weight and reps 0 even thouhg its a complete copy of another set,
//so why cant i make weight and reps of an existing Set 0?
Button {
print("Current sets count: \(sets.count)")
if(sets.count <= 7) {
let weight = set.weight
let reps = set.reps
let newSet = MySet(id: UUID(),weight: weight, reps: reps, isCompleted: false, exercise: exercise)
withAnimation {
sets.append(newSet)
}
}
}
label: {
Image(systemName: "plus.rectangle.on.rectangle")
}
.buttonStyle(.borderless)
}
}
Now the problem is that If the Set already has a Weight and Reps(i.e was added before we navigated to this view) I can't set it to 0 to change it i.e it has 5 reps I want to change it to 6 I cannot clear the 5 to write 6 it wont allow it to be cleared, strangely enough when I add a new set in the New Set button or even then I use the duplicate button to make an exact copy of the set(I can clear it zero for that duplicate one but not the original one)
I am plotting a SwiftUI chart from a Struct that looks like this:
struct BestWeights: Identifiable
{
let id = UUID()
let date: String
let maxWeight: Int
}
I am then creating an array of this Struct that I will use to plot the Chart:
private var bestWeights: [BestWeights] {
let unformattedMonth = DateFormatter().monthSymbols[month - 1]
let formattedMonth = String(unformattedMonth.prefix(3))
bestWeights.append(BestWeights(date: formattedMonth, maxWeight: bestWeightOfPeriod))
//decrementing down one month
selectedDate = Calendar.current.date(byAdding: .month, value: -1, to: selectedDate) ?? selectedDate
Then I am iterating through bestWeights and plotting them:
Chart {
ForEach(bestWeights) { bestWeight in
LineMark(x: .value("Date",bestWeight.date), y: .value("MaxWeight", bestWeight.maxWeight))
.symbol {
Circle()
.fill(.blue)
.frame(width: 7, height: 7)
}
}
}
The problem is that this produces 0 values on the Y axis that scrape the bottom of the LineMark, now I can fix this by not adding all of the bestWeights who's weight is 0 but then I don't get the full x axis I want which is 6 full months, it would show only the number of months as we have records and would jump from February to July etc.. is there any way to remove the 0 weights while keeping the X axis full of dates