Swift ';致命错误:索引超出范围';删除视图中的绑定对象时
在修改子视图依赖于绑定对象的数组时,我在避免索引超出范围错误方面遇到了一些问题 我有一个名为WorkoutList的父视图。WorkoutList具有ActiveWorkoutStore的EnvironmentObject。ActiveWorkoutStore是一个ObservableObject,具有一系列训练对象。我有一个从ActiveWorkoutStore检索的活动训练列表。我使用ForEach循环处理这些活动训练的索引,并将对象绑定传递到名为EditWorkout的子视图,作为NavigationLink的目标。EditWorkout有一个完成训练的按钮,该按钮将其从ActiveWorkoutStore的训练数组中删除,并将其添加到WorkoutHistoryStore。当我从ActiveWorkoutStore的activeWorkouts数组中删除此对象时,我遇到了麻烦,立即导致索引超出范围错误。我怀疑这是因为活动视图依赖于我刚刚删除的绑定对象。我尝试了几种方法,包括将训练传递给EditWorkout,然后使用其id引用ActiveWorkoutStore中的训练来执行我的操作,但遇到了类似的问题。我在网上看到过很多这样的例子,即利用ForEach迭代索引,我已经尽我所能反映了这一点,但我怀疑我可能忽略了这种方法的细微差别 我在下面附上了代码示例。如果您有任何问题或者我还有什么需要包括的,请告诉我!提前感谢您的帮助 工作列表(父视图) 编辑训练(子视图) 运动训练商店 训练Swift ';致命错误:索引超出范围';删除视图中的绑定对象时,swift,swiftui,Swift,Swiftui,在修改子视图依赖于绑定对象的数组时,我在避免索引超出范围错误方面遇到了一些问题 我有一个名为WorkoutList的父视图。WorkoutList具有ActiveWorkoutStore的EnvironmentObject。ActiveWorkoutStore是一个ObservableObject,具有一系列训练对象。我有一个从ActiveWorkoutStore检索的活动训练列表。我使用ForEach循环处理这些活动训练的索引,并将对象绑定传递到名为EditWorkout的子视图,作为Navi
ForEach
为常量范围容器(注意下面的构造函数描述),不允许在构造后修改
扩展ForEach,其中Data==Range,ID==Int,Content:View{
///创建一个实例,该实例通过*常量按需计算视图*
///射程。
///
///此实例只读取'data'的初始值,因此不读取
///需要跨更新识别视图。
///
///要在动态范围内按需计算视图,请使用
///`ForEach(uq:id:content:)`。
public init(u数据:范围,@ViewBuilder内容:@escaping(Int)->content)
}
如果要修改容器,必须使用ForEach(activeWorkout.exercises)中的内容
WorkoutExercise
,EditWorkoutExercise
,WorkoutHistoryStore
,ExerciseStore
?也许最好清理一下这段代码,只留下重要的东西?一般来说,从列表中删除东西风险更大,因为最终可能会出现您描述的问题。更好的方法可能是有一个训练列表,然后为每个训练设置一个workoutState变量(可以是枚举workoutState),该变量可以是“活动”或“完成”。然后,任何需要此数组数据的视图都可以对workoutState变量使用筛选器。您可以在视图中添加if activeWorkout.exerces.index.count>0{//Render List}else{//Render something}
,以检查是否有任何要渲染的内容,同时保持@Published
变量的简单性。。。训练
对象中的数组未发布@是的,抱歉。。。更多的上下文总是有用的!我想我已经找到了答案,并且非常喜欢阿达胡斯推荐的方法,因此我将继续前进。:)@阿达胡斯:我喜欢这个概念。我要做这个改变!非常感谢。非常感谢你。我没有注意到这一点,这确实澄清了我为什么会遇到这个问题。当我将此添加到代码中时,我遇到了“初始化器需要一个主体”错误。知道为什么会发生这种情况吗?
import SwiftUI
struct WorkoutList: View {
@EnvironmentObject var activeWorkoutsStore: ActiveWorkoutStore
@State private var addExercise = false
@State private var workoutInProgress = false
var newWorkoutButton: some View {
Button(action: {
self.activeWorkoutsStore.newActiveWorkout()
}) {
Text("New Workout")
Image(systemName: "plus.circle")
}
}
var body: some View {
NavigationView {
Group {
if activeWorkoutsStore.activeWorkouts.isEmpty {
Text("No active workouts")
} else {
List {
ForEach(activeWorkoutsStore.activeWorkouts.indices.reversed(), id: \.self) { activeWorkoutIndex in
NavigationLink(destination: EditWorkout(activeWorkout: self.$activeWorkoutsStore.activeWorkouts[activeWorkoutIndex])) {
Text(self.activeWorkoutsStore.activeWorkouts[activeWorkoutIndex].id.uuidString)
}
}
}
}
}
.navigationBarTitle(Text("Active Workouts"))
.navigationBarItems(trailing: newWorkoutButton)
}
}
}
//
// EditWorkout.swift
// workout-planner
//
// Created by Dominic Minischetti III on 11/2/19.
// Copyright © 2019 Dominic Minischetti. All rights reserved.
//
import SwiftUI
struct EditWorkout: View {
@EnvironmentObject var workoutHistoryStore: WorkoutHistoryStore
@EnvironmentObject var activeWorkoutStore: ActiveWorkoutStore
@EnvironmentObject var exerciseStore: ExerciseStore
@Environment(\.presentationMode) var presentationMode
@State private var addExercise = false
@Binding var activeWorkout: Workout
var currentDayOfWeek: String {
let weekdayIndex = Calendar.current.component(.weekday, from: Date())
return Calendar.current.weekdaySymbols[weekdayIndex - 1]
}
var chooseExercisesButton: some View {
Button (action: {
self.addExercise = true
}) {
HStack {
Image(systemName: "plus.square")
Text("Choose Exercises")
}
}
.sheet(isPresented: self.$addExercise) {
AddWorkoutExercise(exercises: self.$activeWorkout.exercises)
.environmentObject(self.exerciseStore)
}
}
var saveButton: some View {
Button(action: {
self.workoutHistoryStore.addWorkout(workout: self.$activeWorkout.wrappedValue)
self.activeWorkoutStore.removeActiveWorkout(workout: self.$activeWorkout.wrappedValue)
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Finish Workout")
}
.disabled(self.$activeWorkout.wrappedValue.exercises.isEmpty)
}
var body: some View {
Form {
Section(footer: Text("Choose which exercises are part of this workout")) {
chooseExercisesButton
}
Section(header: Text("Exercises")) {
if $activeWorkout.wrappedValue.exercises.isEmpty {
Text("No exercises")
} else {
ForEach(activeWorkout.exercises.indices, id: \.self) { exerciseIndex in
NavigationLink(destination: EditWorkoutExercise(exercise: self.$activeWorkout.exercises[exerciseIndex])) {
VStack(alignment: .leading) {
Text(self.activeWorkout.exercises[exerciseIndex].name)
Text("\(self.activeWorkout.exercises[exerciseIndex].sets.count) Set\(self.activeWorkout.exercises[exerciseIndex].sets.count == 1 ? "" : "s")")
.font(.footnote)
.opacity(0.5)
}
}
}
saveButton
}
}
}
.navigationBarTitle(Text("Edit Workout"), displayMode: .inline )
}
}
import Foundation
import Combine
class ActiveWorkoutStore: ObservableObject {
@Published var activeWorkouts: [Workout] = []
func newActiveWorkout() {
activeWorkouts.append(Workout())
}
func saveActiveWorkout(workout: Workout) {
let workoutIndex = activeWorkouts.firstIndex(where: { $0.id == workout.id })!
activeWorkouts[workoutIndex] = workout
}
func removeActiveWorkout(workout: Workout) {
if let workoutIndex = activeWorkouts.firstIndex(where: { $0.id == workout.id }) {
activeWorkouts.remove(at: workoutIndex)
}
}
}
import SwiftUI
struct Workout: Hashable, Codable, Identifiable {
var id = UUID()
var date = Date()
var exercises: [WorkoutExercise] = []
}
extension ForEach where Data == Range<Int>, ID == Int, Content : View {
/// Creates an instance that computes views on demand over a *constant*
/// range.
///
/// This instance only reads the initial value of `data` and so it does not
/// need to identify views across updates.
///
/// To compute views on demand over a dynamic range use
/// `ForEach(_:id:content:)`.
public init(_ data: Range<Int>, @ViewBuilder content: @escaping (Int) -> Content)
}