Anonymous
SwiftData — массив отношений не остается в порядке
Сообщение
Anonymous » 19 апр 2024, 19:29
Заранее извините, я знаю, что это кусок кода, который я еще не оптимизировал и не разделил на части.
Когда я добавляю «ExercisionSets» в «Exercision» около строки 132 или удаляю таким же образом, новая запись не всегда помещается в конец, или массив немного сбивается. Почему это и как я могу это решить? Надеюсь запустить это приложение завтра, так что скрестим пальцы.
Код: Выделить всё
import SwiftUI
import SwiftData
import Foundation
import Flow
struct ExercisePlanDayView: View {
@AppStorage("useMetric") var useMetric: Bool = false
@Environment(\.modelContext) var modelContext
@Query(sort: [SortDescriptor(\Exercise.orderIndex)]) var exercises: [Exercise]
@Binding var isEditing: Bool
@Binding var isExpanded: Bool
@State var sortOrder = SortDescriptor(\Exercise.orderIndex)
@State private var selectedSet: ExerciseSet = ExerciseSet()
@State private var selectedExercise: Exercise = Exercise()
var selectedDay: Int
let columns = [
GridItem(.flexible(minimum: 50, maximum: 100))
]
init(selectedDay: Int, isEditing: Binding, isExpanded: Binding) {
self.selectedDay = selectedDay
_exercises = Query(filter: #Predicate {
return $0.weekday == selectedDay
}, sort: [SortDescriptor(\Exercise.orderIndex)])
_isEditing = isEditing
_isExpanded = isExpanded
}
let weekDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
var body: some View {
ScrollView {
VStack {
if (exercises.filter({$0.weekday == selectedDay}).count == 0 && !isEditing) {
Spacer()
Text("Tap The Pencil To Add Exercises")
.font(.title2)
.foregroundStyle(.baseInvert)
.animation(.easeInOut, value: exercises)
.transition(.opacity)
Spacer()
}
ForEach(exercises, id: \.self) { exercise in
VStack (spacing: 5) {
HStack (spacing: 0) {
if (isEditing) {
Button(action: {
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
withAnimation {
modelContext.delete(exercise)
do {
try modelContext.save()
} catch {
print("\(error.localizedDescription)")
}
}
}, label: {
Image(systemName: "minus.circle.fill")
.foregroundStyle(.white, .red)
.frame(width: 15, height: 15)
})
.buttonStyle(.plain)
Button(action: {
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
withAnimation {
if (selectedExercise.id == exercise.id) {
exercise.name = selectedExercise.name
selectedExercise = Exercise()
} else {
selectedExercise = exercise
}
}
}, label: {
if (selectedExercise.id == exercise.id) {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.white, .green)
.frame(width: 15, height: 15)
} else {
Image(systemName: "ellipsis.rectangle")
.foregroundStyle(.white, .green)
.frame(width: 15, height: 15)
}
})
.padding(.leading, 20)
.buttonStyle(.plain)
}
if (exercise.id == selectedExercise.id && isEditing) {
TextField("Exercise Name", text: $selectedExercise.name)
.textFieldStyle(RoundedBorderTextFieldStyle())
.onChange(of: selectedExercise.name) {
withAnimation {
exercise.name = selectedExercise.name
}
}
.frame(maxWidth: 200, alignment: .leading)
.scaleEffect(0.9)
} else {
Text(exercise.name)
.padding(.horizontal, 5)
.fontWeight(.semibold)
.frame(maxWidth: 200, alignment: .leading)
.foregroundStyle(.baseInvert)
.truncationMode(.tail)
}
Spacer()
if (isEditing) {
Button(action: {
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
withAnimation {
let newSet = ExerciseSet(goalType: .duration, repetitionsToDo: 0, durationToDo: 30, weightToLift: 30)
exercise.exerciseSets.append(newSet)
modelContext.insert(newSet)
do {
try modelContext.save()
} catch {
print("\(error.localizedDescription)")
}
selectedSet = newSet
}
}, label: {
HStack (spacing: 0) {
Image(systemName: "plus.circle.fill")
.foregroundStyle(.white, .green)
.frame(height: 15)
Text("Set")
.bold()
.foregroundStyle(.baseInvert)
.padding(.horizontal, 2)
.padding(.vertical, 2)
.shadow(radius: 5)
}
})
.buttonStyle(.plain)
}
}
.animation(.easeInOut, value: exercises.count)
.transition(.move(edge: .leading))
VStack (spacing: 0) {
if (exercise.exerciseSets.count == 0 ) {
HStack {
Text("No sets added yet")
.font(.caption)
.foregroundStyle(.baseInvert)
.animation(.easeInOut, value: exercises)
.transition(.opacity)
Spacer()
}
.padding(.leading)
}
if (isEditing) {
ForEach(exercise.exerciseSets, id: \.self) { set in
HStack {
ExerciseSetEditor(set: set)
.padding(.bottom, 4)
Button(action: {
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
withAnimation {
exercise.exerciseSets.remove(at: exercise.exerciseSets.firstIndex(of: set) ?? 0)
modelContext.delete(set)
do {
try modelContext.save()
} catch {
print("\(error.localizedDescription)")
}
}
}, label: {
Image(systemName: "minus.circle.fill")
.foregroundStyle(.white, .red)
.frame(width: 15, height: 15)
})
.buttonStyle(.plain)
}
}
} else {
HFlow(itemSpacing: 4, rowSpacing: 6) {
ForEach(exercise.exerciseSets, id: \.self) { set in
if (selectedSet.id == set.id) {
HStack {
VStack {
if selectedSet.goalType == .duration {
Stepper(value: Binding(
get: { selectedSet.durationToDo },
set: { selectedSet.durationToDo = $0 }
), label: {
HStack {
Image(systemName: "stopwatch")
.foregroundStyle(.greenEnd)
Text("\(selectedSet.durationToDo)")
.bold()
.foregroundStyle(.baseInvert)
}
})
} else {
Stepper(value: Binding(
get: { selectedSet.weightToLift },
set: { selectedSet.weightToLift = $0 }
), label: {
HStack {
Image(systemName: "scalemass")
.foregroundStyle(.baseInvert)
Text("\(selectedSet.weightToLift)")
.bold()
.foregroundStyle(.baseInvert)
}
})
Stepper(value: Binding(
get: { selectedSet.repetitionsToDo },
set: { selectedSet.repetitionsToDo = $0 }
), label: {
HStack {
Image(systemName: "repeat")
.foregroundStyle(.orangeEnd)
Text("\(selectedSet.repetitionsToDo)")
.bold()
.foregroundStyle(.baseInvert)
}
})
}
}
.frame(width: 200, alignment: .leading)
Spacer()
Button(action: {
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
withAnimation {
selectedSet = ExerciseSet()
}
}, label: {
Text("Save")
.foregroundStyle(.baseInvert)
.bold()
.padding(10)
})
.buttonStyle(.plain)
.background {
ZStack {
Rectangle()
.cornerRadius(5)
.foregroundStyle(.blue)
.shadow(radius: 5)
}
.compositingGroup()
}
.padding(.trailing)
}
.frame(width: 300)
.padding(3)
.background {
ZStack {
Rectangle()
.foregroundStyle(.thickMaterial)
.shadow(radius: 5)
Rectangle()
.blendMode(.destinationOut)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(.baseInvert, lineWidth: 1)
)
}
.compositingGroup()
}
.animation(.easeInOut, value: selectedSet)
.transition(.scale(scale: 0.1, anchor: .leading))
} else {
HStack (alignment: .center) {
Button(action: {
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
withAnimation {
selectedSet = set
}
}, label: {
HStack {
if (set.goalType == .duration) {
Image(systemName: "stopwatch")
.foregroundStyle(.greenEnd)
Text(set.durationToDo.description)
.foregroundStyle(.base)
} else {
Image(systemName: "scalemass")
.foregroundStyle(.baseAccent)
Text(set.weightToLift.description)
.foregroundStyle(.base)
Image(systemName: "repeat")
.foregroundStyle(.orangeEnd)
Text(set.repetitionsToDo.description)
.foregroundStyle(.base)
}
}
.lineLimit(1)
.padding(3)
.background {
Rectangle()
.foregroundStyle(.baseInvert)
.cornerRadius(5)
}
})
.buttonStyle(.plain)
}
.animation(.easeInOut, value: exercises.count)
.transition(.move(edge: .leading).combined(with: .opacity))
}
}
}
}
}
.padding(.bottom, 5)
}
}
if (isEditing) {
HStack {
Button(action: {
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
withAnimation {
addExercise(currentLength: exercises.count, weekday: selectedDay)
}
}, label: {
HStack (spacing: 0) {
Text("Exercise")
.bold()
.foregroundStyle(.baseInvert)
.padding(.horizontal, 2)
.padding(.vertical, 2)
.shadow(radius: 5)
Image(systemName: "plus.circle.fill")
.foregroundStyle(.white, .green)
.frame(height: 15)
}
})
.buttonStyle(.plain)
Spacer()
}
.animation(.easeInOut, value: exercises.count)
.transition(.move(edge: .leading))
}
}
.padding(.horizontal, 5)
}
.scrollDisabled(!isEditing && !isExpanded)
.onChange(of: isEditing) {
if (!isEditing) {
selectedSet = ExerciseSet()
selectedExercise = Exercise()
}
}
}
func addExercise(currentLength: Int, weekday: Int) {
let exercise = Exercise(weekday: weekday, orderIndex: currentLength + 1)
modelContext.insert(exercise)
selectedExercise = exercise
}
}
import Foundation
import SwiftData
@Model
class Exercise: Identifiable {
var id: UUID
var weekday: Int
var orderIndex: Int
var name: String
@Relationship(deleteRule: .cascade) var exerciseSets = [ExerciseSet]()
init(id: UUID = UUID(), weekday: Int = 0, orderIndex: Int = 0, name: String = "Unnamed Exercise") {
self.id = id
self.weekday = weekday
self.orderIndex = orderIndex
self.name = name
}
}
@Model
class ExerciseSet: Identifiable {
var id: UUID
var goalType: GoalType
var repetitionsToDo: Int
var durationToDo: Int
var weightToLift: Int
var lastRepetitionsRecorded: Int
var lastDurationRecorded: Int
var lastWeightRecorded: Int
init(goalType: GoalType = .weight, repetitionsToDo: Int = 0, durationToDo: Int = 0, weightToLift: Int = 0, lastRepetitionsRecorded: Int = 0, lastDurationRecorded: Int = 0, lastWeightRecorded: Int = 0) {
self.id = UUID()
self.goalType = goalType
self.repetitionsToDo = repetitionsToDo
self.durationToDo = durationToDo
self.weightToLift = weightToLift
self.lastRepetitionsRecorded = lastRepetitionsRecorded
self.lastDurationRecorded = lastDurationRecorded
self.lastWeightRecorded = lastWeightRecorded
}
}
Я пробовал использовать различные способы сортировки постфактум, например, используя целочисленное свойство «order» в объектах «ExercisionSet». Я также пробовал множество видов помощи ИИ.
Подробнее здесь:
https://stackoverflow.com/questions/783 ... g-in-order
1713544196
Anonymous
Заранее извините, я знаю, что это кусок кода, который я еще не оптимизировал и не разделил на части. Когда я добавляю «ExercisionSets» в «Exercision» около строки 132 или удаляю таким же образом, новая запись не всегда помещается в конец, или массив немного сбивается. Почему это и как я могу это решить? Надеюсь запустить это приложение завтра, так что скрестим пальцы. [code]import SwiftUI import SwiftData import Foundation import Flow struct ExercisePlanDayView: View { @AppStorage("useMetric") var useMetric: Bool = false @Environment(\.modelContext) var modelContext @Query(sort: [SortDescriptor(\Exercise.orderIndex)]) var exercises: [Exercise] @Binding var isEditing: Bool @Binding var isExpanded: Bool @State var sortOrder = SortDescriptor(\Exercise.orderIndex) @State private var selectedSet: ExerciseSet = ExerciseSet() @State private var selectedExercise: Exercise = Exercise() var selectedDay: Int let columns = [ GridItem(.flexible(minimum: 50, maximum: 100)) ] init(selectedDay: Int, isEditing: Binding, isExpanded: Binding) { self.selectedDay = selectedDay _exercises = Query(filter: #Predicate { return $0.weekday == selectedDay }, sort: [SortDescriptor(\Exercise.orderIndex)]) _isEditing = isEditing _isExpanded = isExpanded } let weekDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] var body: some View { ScrollView { VStack { if (exercises.filter({$0.weekday == selectedDay}).count == 0 && !isEditing) { Spacer() Text("Tap The Pencil To Add Exercises") .font(.title2) .foregroundStyle(.baseInvert) .animation(.easeInOut, value: exercises) .transition(.opacity) Spacer() } ForEach(exercises, id: \.self) { exercise in VStack (spacing: 5) { HStack (spacing: 0) { if (isEditing) { Button(action: { UIImpactFeedbackGenerator(style: .medium).impactOccurred() withAnimation { modelContext.delete(exercise) do { try modelContext.save() } catch { print("\(error.localizedDescription)") } } }, label: { Image(systemName: "minus.circle.fill") .foregroundStyle(.white, .red) .frame(width: 15, height: 15) }) .buttonStyle(.plain) Button(action: { UIImpactFeedbackGenerator(style: .medium).impactOccurred() withAnimation { if (selectedExercise.id == exercise.id) { exercise.name = selectedExercise.name selectedExercise = Exercise() } else { selectedExercise = exercise } } }, label: { if (selectedExercise.id == exercise.id) { Image(systemName: "checkmark.circle.fill") .foregroundStyle(.white, .green) .frame(width: 15, height: 15) } else { Image(systemName: "ellipsis.rectangle") .foregroundStyle(.white, .green) .frame(width: 15, height: 15) } }) .padding(.leading, 20) .buttonStyle(.plain) } if (exercise.id == selectedExercise.id && isEditing) { TextField("Exercise Name", text: $selectedExercise.name) .textFieldStyle(RoundedBorderTextFieldStyle()) .onChange(of: selectedExercise.name) { withAnimation { exercise.name = selectedExercise.name } } .frame(maxWidth: 200, alignment: .leading) .scaleEffect(0.9) } else { Text(exercise.name) .padding(.horizontal, 5) .fontWeight(.semibold) .frame(maxWidth: 200, alignment: .leading) .foregroundStyle(.baseInvert) .truncationMode(.tail) } Spacer() if (isEditing) { Button(action: { UIImpactFeedbackGenerator(style: .medium).impactOccurred() withAnimation { let newSet = ExerciseSet(goalType: .duration, repetitionsToDo: 0, durationToDo: 30, weightToLift: 30) exercise.exerciseSets.append(newSet) modelContext.insert(newSet) do { try modelContext.save() } catch { print("\(error.localizedDescription)") } selectedSet = newSet } }, label: { HStack (spacing: 0) { Image(systemName: "plus.circle.fill") .foregroundStyle(.white, .green) .frame(height: 15) Text("Set") .bold() .foregroundStyle(.baseInvert) .padding(.horizontal, 2) .padding(.vertical, 2) .shadow(radius: 5) } }) .buttonStyle(.plain) } } .animation(.easeInOut, value: exercises.count) .transition(.move(edge: .leading)) VStack (spacing: 0) { if (exercise.exerciseSets.count == 0 ) { HStack { Text("No sets added yet") .font(.caption) .foregroundStyle(.baseInvert) .animation(.easeInOut, value: exercises) .transition(.opacity) Spacer() } .padding(.leading) } if (isEditing) { ForEach(exercise.exerciseSets, id: \.self) { set in HStack { ExerciseSetEditor(set: set) .padding(.bottom, 4) Button(action: { UIImpactFeedbackGenerator(style: .medium).impactOccurred() withAnimation { exercise.exerciseSets.remove(at: exercise.exerciseSets.firstIndex(of: set) ?? 0) modelContext.delete(set) do { try modelContext.save() } catch { print("\(error.localizedDescription)") } } }, label: { Image(systemName: "minus.circle.fill") .foregroundStyle(.white, .red) .frame(width: 15, height: 15) }) .buttonStyle(.plain) } } } else { HFlow(itemSpacing: 4, rowSpacing: 6) { ForEach(exercise.exerciseSets, id: \.self) { set in if (selectedSet.id == set.id) { HStack { VStack { if selectedSet.goalType == .duration { Stepper(value: Binding( get: { selectedSet.durationToDo }, set: { selectedSet.durationToDo = $0 } ), label: { HStack { Image(systemName: "stopwatch") .foregroundStyle(.greenEnd) Text("\(selectedSet.durationToDo)") .bold() .foregroundStyle(.baseInvert) } }) } else { Stepper(value: Binding( get: { selectedSet.weightToLift }, set: { selectedSet.weightToLift = $0 } ), label: { HStack { Image(systemName: "scalemass") .foregroundStyle(.baseInvert) Text("\(selectedSet.weightToLift)") .bold() .foregroundStyle(.baseInvert) } }) Stepper(value: Binding( get: { selectedSet.repetitionsToDo }, set: { selectedSet.repetitionsToDo = $0 } ), label: { HStack { Image(systemName: "repeat") .foregroundStyle(.orangeEnd) Text("\(selectedSet.repetitionsToDo)") .bold() .foregroundStyle(.baseInvert) } }) } } .frame(width: 200, alignment: .leading) Spacer() Button(action: { UIImpactFeedbackGenerator(style: .medium).impactOccurred() withAnimation { selectedSet = ExerciseSet() } }, label: { Text("Save") .foregroundStyle(.baseInvert) .bold() .padding(10) }) .buttonStyle(.plain) .background { ZStack { Rectangle() .cornerRadius(5) .foregroundStyle(.blue) .shadow(radius: 5) } .compositingGroup() } .padding(.trailing) } .frame(width: 300) .padding(3) .background { ZStack { Rectangle() .foregroundStyle(.thickMaterial) .shadow(radius: 5) Rectangle() .blendMode(.destinationOut) .overlay( RoundedRectangle(cornerRadius: 5) .stroke(.baseInvert, lineWidth: 1) ) } .compositingGroup() } .animation(.easeInOut, value: selectedSet) .transition(.scale(scale: 0.1, anchor: .leading)) } else { HStack (alignment: .center) { Button(action: { UIImpactFeedbackGenerator(style: .medium).impactOccurred() withAnimation { selectedSet = set } }, label: { HStack { if (set.goalType == .duration) { Image(systemName: "stopwatch") .foregroundStyle(.greenEnd) Text(set.durationToDo.description) .foregroundStyle(.base) } else { Image(systemName: "scalemass") .foregroundStyle(.baseAccent) Text(set.weightToLift.description) .foregroundStyle(.base) Image(systemName: "repeat") .foregroundStyle(.orangeEnd) Text(set.repetitionsToDo.description) .foregroundStyle(.base) } } .lineLimit(1) .padding(3) .background { Rectangle() .foregroundStyle(.baseInvert) .cornerRadius(5) } }) .buttonStyle(.plain) } .animation(.easeInOut, value: exercises.count) .transition(.move(edge: .leading).combined(with: .opacity)) } } } } } .padding(.bottom, 5) } } if (isEditing) { HStack { Button(action: { UIImpactFeedbackGenerator(style: .medium).impactOccurred() withAnimation { addExercise(currentLength: exercises.count, weekday: selectedDay) } }, label: { HStack (spacing: 0) { Text("Exercise") .bold() .foregroundStyle(.baseInvert) .padding(.horizontal, 2) .padding(.vertical, 2) .shadow(radius: 5) Image(systemName: "plus.circle.fill") .foregroundStyle(.white, .green) .frame(height: 15) } }) .buttonStyle(.plain) Spacer() } .animation(.easeInOut, value: exercises.count) .transition(.move(edge: .leading)) } } .padding(.horizontal, 5) } .scrollDisabled(!isEditing && !isExpanded) .onChange(of: isEditing) { if (!isEditing) { selectedSet = ExerciseSet() selectedExercise = Exercise() } } } func addExercise(currentLength: Int, weekday: Int) { let exercise = Exercise(weekday: weekday, orderIndex: currentLength + 1) modelContext.insert(exercise) selectedExercise = exercise } } import Foundation import SwiftData @Model class Exercise: Identifiable { var id: UUID var weekday: Int var orderIndex: Int var name: String @Relationship(deleteRule: .cascade) var exerciseSets = [ExerciseSet]() init(id: UUID = UUID(), weekday: Int = 0, orderIndex: Int = 0, name: String = "Unnamed Exercise") { self.id = id self.weekday = weekday self.orderIndex = orderIndex self.name = name } } @Model class ExerciseSet: Identifiable { var id: UUID var goalType: GoalType var repetitionsToDo: Int var durationToDo: Int var weightToLift: Int var lastRepetitionsRecorded: Int var lastDurationRecorded: Int var lastWeightRecorded: Int init(goalType: GoalType = .weight, repetitionsToDo: Int = 0, durationToDo: Int = 0, weightToLift: Int = 0, lastRepetitionsRecorded: Int = 0, lastDurationRecorded: Int = 0, lastWeightRecorded: Int = 0) { self.id = UUID() self.goalType = goalType self.repetitionsToDo = repetitionsToDo self.durationToDo = durationToDo self.weightToLift = weightToLift self.lastRepetitionsRecorded = lastRepetitionsRecorded self.lastDurationRecorded = lastDurationRecorded self.lastWeightRecorded = lastWeightRecorded } } [/code] Я пробовал использовать различные способы сортировки постфактум, например, используя целочисленное свойство «order» в объектах «ExercisionSet». Я также пробовал множество видов помощи ИИ. Подробнее здесь: [url]https://stackoverflow.com/questions/78355085/swiftdata-relationship-array-not-staying-in-order[/url]