这是我的数据结构
struct SPPWorkout: Codable {
static let setKey = "Sets"
static let exerciseID = "id"
var id: Double? = 0.0
var duration: String?
var calories: Int?
var date: String?
var exercises: [ExerciseSet]
[...]
}
struct ExerciseSet: Codable {
let id: String
let name: String
var reps: Int
var weight: Double
[...]
}
extension ExerciseSet: Equatable {
static func ==(lhs: ExerciseSet, rhs: ExerciseSet) -> Bool {
return lhs.id == rhs.id
}
}
在SwiftUI视图中,我尝试从用户输入修改ExerciseSet
@State private var sppWorkout: SPPWorkout!
EditSetPopup(isShowingOverlay: $isShowingOverlay,
update: { reps, weight in
guard let editingIndex = editingIndex else { return }
sppWorkout.exercises[editingIndex].reps = Int(reps) ?? 0
sppWorkout.exercises[editingIndex].weight = Double(weight) ?? 0.0
self.editingIndex = nil
})
}
问题就在这里
sppWorkout.exercises[editingIndex].reps = Int(reps) ?? 0
sppWorkout.exercises[editingIndex].weight = Double(weight) ??
我已经尝试了所有方法来更新它,无论是从视图还是使用SPPWorkout中的函数。我还尝试替换索引中的对象
var newSet = ExerciseSet(id: [...], newValues)
self.exercises[editingIndex] = newSet
但它绝对不想更新。我确信它会在某个地方创建一个副本,然后对其进行编辑,但我不知道为什么以及如何设置新值。
编辑:如果我试着删除一些东西,它是好的
sppWorkout.exercises.removeAll(where: { $0 == sppWorkout.exercises[index]})
**编辑2:**传递Guard语句,不更改数组中的值。
**编辑3:*在下面Jared的建议下,我已经将现有数组复制到新数组中,设置新值,然后尝试将新值分配给原始数组,但它仍然不会覆盖。
EditSetPopup(isShowingOverlay: $isShowingOverlay,
update: { reps, weight in
print(sppWorkout.exercises)
guard let editingIndex = editingIndex else { return }
var copyOfTheArray = sppWorkout.exercises
copyOfTheArray[editingIndex].reps = Int(reps) ?? 0
copyOfTheArray[editingIndex].weight = Double(weight) ?? 0.0
//Copy of the array is updated correctly, it has the new values
sppWorkout.exercises = copyOfTheArray
//Original array doesn't get overwritten. It still has old values
self.editingIndex = nil
**编辑4:*通过将模型提取到视图模型中并更新那里的值,我成功地取得了进展。现在sppWorkout中的值得到了更新,但即使我调用了objectWillChange.send()
,UI更新也不会触发。
完整代码:
class WorkoutDetailsViewModel: ObservableObject {
var workoutID: String!
@Published var sppWorkout: SPPWorkout!
func setupData(with workoutID: String) {
sppWorkout = FileIOManager.readWorkout(with: workoutID)
}
func update(_ index: Int, newReps: Int, newWeight: Double) {
let oldOne = sppWorkout.exercises[index]
let update = ExerciseSet(id: oldOne.id, name: oldOne.name, reps: newReps, weight: newWeight)
sppWorkout.exercises[index] = update
self.objectWillChange.send()
}
}
struct WorkoutDetailsView: View {
var workoutID: String!
@StateObject private var viewModel = WorkoutDetailsViewModel()
var workout: HKWorkout
var dateFormatter: DateFormatter
@State private var offset = 0
@State private var isShowingOverlay = false
@State private var editingIndex: Int?
@EnvironmentObject var settingsManager: SettingsManager
@Environment(\.dismiss) private var dismiss
var body: some View {
if viewModel.sppWorkout != nil {
VStack {
ListWorkoutItem(workout: workout, dateFormatter: dateFormatter)
.padding([.leading, .trailing], 10.0)
List(viewModel.sppWorkout.exercises, id: \.id) { exercise in
let index = viewModel.sppWorkout.exercises.firstIndex(of: exercise) ?? 0
DetailListSetItem(exerciseSet: viewModel.sppWorkout.exercises[index], set: index + 1)
.environmentObject(settingsManager)
.swipeActions {
Button(role: .destructive, action: {
viewModel.sppWorkout.exercises.removeAll(where: { $0 == viewModel.sppWorkout.exercises[index]})
} ) {
Label("Delete", systemImage: "trash")
}
Button(role: .none, action: {
isShowingOverlay = true
editingIndex = index
} ) {
Label("Edit", systemImage: "pencil")
}.tint(.blue)
}
}
.padding([.leading, .trailing], -30)
//iOS 16 .scrollContentBackground(.hidden)
}
.overlay(alignment: .bottom, content: {
editOverlay
.animation(.easeInOut (duration: 0.5), value: isShowingOverlay)
})
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action : {
do {
try FileIOManager.write(viewModel.sppWorkout, toDocumentNamed: "\(viewModel.sppWorkout.id ?? 0).json")
} catch {
Debugger.log(error: error.localizedDescription)
}
dismiss()
}){
Image(systemName: "arrow.left")
})
} else {
Text("No workout details found")
.italic()
.fontWeight(.bold)
.font(.system(size: 35))
.onAppear(perform: {
viewModel.setupData(with: workoutID)
})
}
}
@ViewBuilder private var editOverlay: some View {
if isShowingOverlay {
ZStack {
Button {
isShowingOverlay = false
} label: {
Color.clear
}
.edgesIgnoringSafeArea(.all)
VStack{
Spacer()
EditSetPopup(isShowingOverlay: $isShowingOverlay,
update: { reps, weight in
guard let editingIndex = editingIndex else { return }
print(viewModel.sppWorkout.exercises)
print("dupa aia:\n")
viewModel.update(editingIndex, newReps: Int(reps) ?? 0, newWeight: Double(weight) ?? 0.0)
print(viewModel.sppWorkout.exercises)
self.editingIndex = nil
})
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color("popupBackground"),
lineWidth: 3)
)
}
}
}
}
}
2条答案
按热度按时间k5ifujac1#
所以我在Reddit上得到了一个很好的解释,说明是什么导致了这个问题。如果你正在阅读这篇文章,谢谢你U/Neddy-Seagoon。
这个解释是
。我相信更新数组不会触发状态更新。对于数组,唯一会发生的事情是如果计数发生变化。因此,sppWorkout.exerces[index].reps=newRep不会导致触发。这不会更改viewModel.sppWorkout.exercises.index
所以我所要做的就是修改我的清单
至
因为这会触发列表更新,因为当更新列表中条目的属性时,hashValue确实会改变。
bttbmeg02#
行
List(viewModel.sppWorkout.exercises, id: \.id) { exercise in
替换为
List(viewModel.sppWorkout.exercises, id: \.self) { exercise in