SwiftData — массив отношений не остается в порядкеIOS

Программируем под IOS
Ответить
Anonymous
 SwiftData — массив отношений не остается в порядке

Сообщение Anonymous »

Заранее извините, я знаю, что это кусок кода, который я еще не оптимизировал и не разделил на части.
Когда я добавляю «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
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «IOS»