删除视图中的绑定对象时出现“致命错误:索引超出范围" [英] 'Fatal error: index out of range' when deleting bound object in view
问题描述
在修改子视图依赖于绑定对象的数组时,我在避免索引超出范围错误时遇到了一些麻烦.
我有一个名为 WorkoutList 的父视图.WorkoutList 有一个 ActiveWorkoutStore 的 EnvironmentObject.ActiveWorkoutStore 是一个 ObservableObject,它具有一组 Workout 对象.我有一个从 ActiveWorkoutStore 中检索的活动锻炼列表.我正在使用 ForEach 循环来处理这些活动锻炼的索引,并将对象绑定传递到名为 EditWorkout 的子视图作为 NavigationLink 的目标.EditWorkout 有一个用于完成锻炼的按钮,该按钮可将其从 ActiveWorkoutStore 的锻炼数组中删除,并将其添加到 WorkoutHistoryStore.当我从 ActiveWorkoutStore 的 activeWorkouts 数组中删除这个对象时,我遇到了麻烦,立即导致索引超出范围错误.我怀疑这是因为活动视图依赖于我刚刚删除的绑定对象.我已经尝试了几个这样的排列,包括将锻炼传递给 EditWorkout,然后使用它的 id 引用 ActiveWorkoutStore 中的锻炼来执行我的操作,但遇到了类似的问题.我在网上看到了很多示例,它们遵循这种利用 ForEach 迭代索引的模式,并且我已经尽我所能对其进行了镜像,但我怀疑我可能遗漏了该方法的细微差别.
我在下面附上了代码示例.如果您有任何问题,或者还有什么我应该包括的内容,请告诉我!预先感谢您的帮助!
锻炼列表(父视图)
导入 SwiftUI结构体锻炼列表:查看 {@EnvironmentObject var activeWorkoutsStore: ActiveWorkoutStore@State 私有变量 addExercise = false@State 私有变量锻炼InProgress = falsevar newWorkoutButton:一些视图{按钮(动作:{self.activeWorkoutsStore.newActiveWorkout()}) {文本(新锻炼")图像(系统名称:plus.circle")}}var主体:一些视图{导航视图{团体 {如果 activeWorkoutsStore.activeWorkouts.isEmpty {文本(没有积极的锻炼")} 别的 {列表 {ForEach(activeWorkoutsStore.activeWorkouts.indices.reversed(), id: \.self) { activeWorkoutIndex inNavigationLink(目的地:EditWorkout(activeWorkout:self.$activeWorkoutsStore.activeWorkouts[activeWorkoutIndex])){文本(self.activeWorkoutsStore.activeWorkouts[activeWorkoutIndex].id.uuidString)}}}}}.navigationBarTitle(Text(Active Workouts")).navigationBarItems(尾随:newWorkoutButton)}}}
EditWorkout(子视图)
<代码>////编辑Workout.swift//锻炼计划器////由 Dominic Minischetti III 于 2019 年 11 月 2 日创建.//版权所有 © 2019 Dominic Minischetti.版权所有.//导入 SwiftUIstruct EditWorkout:查看{@EnvironmentObject var trainingHistoryStore: WorkoutHistoryStore@EnvironmentObject var activeWorkoutStore: ActiveWorkoutStore@EnvironmentObject var 运动商店:运动商店@Environment(\.presentationMode) varpresentationMode@State 私有变量 addExercise = false@Binding var activeWorkout:锻炼var currentDayOfWeek:字符串{让 weekdayIndex = Calendar.current.component(.weekday, from: Date())返回 Calendar.current.weekdaySymbols[weekdayIndex - 1]}var chooseExercisesButton:一些视图{按钮(动作:{self.addExercise = true}) {堆栈{图像(系统名称:plus.square")文本(选择练习")}}.sheet(isPresented: self.$addExercise) {AddWorkoutExercise(练习:self.$activeWorkout.exercises).environmentObject(self.exerciseStore)}}var saveButton:一些视图{按钮(动作:{self.workoutHistoryStore.addWorkout(锻炼:self.$activeWorkout.wrappedValue)self.activeWorkoutStore.removeActiveWorkout(锻炼:self.$activeWorkout.wrappedValue)self.presentationMode.wrappedValue.dismiss()}) {文本(完成锻炼")}.disabled(self.$activeWorkout.wrappedValue.exercises.isEmpty)}var主体:一些视图{形式 {Section(footer: Text(选择哪些练习是本次锻炼的一部分")) {选择练习按钮}部分(标题:文本(练习")){如果 $activeWorkout.wrappedValue.exercises.isEmpty {文本(没有练习")} 别的 {ForEach(activeWorkout.exercises.indices, id: \.self) {executionIndex inNavigationLink(destination: EditWorkoutExercise(exercise: self.$activeWorkout.exercises[exerciseIndex])) {VStack(对齐:.领先){文本(self.activeWorkout.exercises[exerciseIndex].name)Text("\(self.activeWorkout.exercises[exerciseIndex].sets.count) Set\(self.activeWorkout.exercises[exerciseIndex].sets.count == 1 ?"": "s")").font(.footnote).不透明度(0.5)}}}保存按钮}}}.navigationBarTitle(Text(Edit Workout"), displayMode: .inline )}}
ActiveWorkoutStore
导入基础进口结合类 ActiveWorkoutStore:ObservableObject {@Published var activeWorkouts: [锻炼] = []func newActiveWorkout() {activeWorkouts.append(Workout())}func saveActiveWorkout(锻炼:锻炼){让锻炼索引 = activeWorkouts.firstIndex(其中:{ $0.id == training.id })!activeWorkouts[workoutIndex] = 锻炼}func removeActiveWorkout(锻炼:锻炼){如果让锻炼索引 = activeWorkouts.firstIndex(where: { $0.id == training.id }) {activeWorkouts.remove(at: trainingIndex)}}}
锻炼
导入 SwiftUIstruct 锻炼:可哈希、可编码、可识别 {var id = UUID()var 日期 = 日期()var 练习:[WorkoutExercise] = []}
ForEach
是常量范围容器(注意下面对构造函数的描述),之后不允许修改施工.
extension ForEach where Data == Range, ID == Int, Content : View {///创建一个实例,通过*常量*按需计算视图///范围.//////这个实例只读取`data`的初始值,所以它不会///需要识别跨更新的视图.//////在动态范围内按需计算视图使用///`ForEach(_:id:content:)`.public init(_ data: Range, @ViewBuilder content: @escaping (Int) -> Content)}
如果要修改容器,必须使用ForEach(activeWorkout.exercises)
I am having some trouble avoiding index out of range errors when modifying an array that a child view depends on a bound object from.
I have a parent view called WorkoutList. WorkoutList has an EnvironmentObject of ActiveWorkoutStore. ActiveWorkoutStore is an ObservableObject that has an array of Workout objects. I have a list of active workouts being retrieved from ActiveWorkoutStore. I'm using a ForEach loop to work with the indices of these active workouts and pass an object binding to a child view called EditWorkout as a destination for a NavigationLink. EditWorkout has a button to finish a workout, which removes it from ActiveWorkoutStore's array of workouts and adds it to WorkoutHistoryStore. I'm running into trouble when I remove this object from ActiveWorkoutStore's activeWorkouts array, immediately causing an index out of range error. I'm suspecting this is because the active view relies on a bound object that I've just deleted. I've tried a couple permutations of this, including passing a workout to EditWorkout, then using its id to reference a workout in ActiveWorkoutStore to perform my operations, but run into similar troubles. I've seen a lot of examples online that follow this pattern of leveraging ForEach to iterate over indices and I've mirrored it as best I can tell, but I suspect I may be missing a nuance to the approach.
I've attached code samples below. Let me know if you have any questions or if there's anything else I should include! Thanks in advance for your help!
WorkoutList (Parent View)
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 (Child View)
//
// 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 )
}
}
ActiveWorkoutStore
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)
}
}
}
Workout
import SwiftUI
struct Workout: Hashable, Codable, Identifiable {
var id = UUID()
var date = Date()
var exercises: [WorkoutExercise] = []
}
ForEach<Range>
is constant range container (pay attention on below description of constructor), it is not allowed to modify it after construction.
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) }
If you want to modify container, you have to use ForEach(activeWorkout.exercises)
这篇关于删除视图中的绑定对象时出现“致命错误:索引超出范围"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!