将 Slider 值绑定到 EnvironmentObject 中的嵌套数组时索引超出范围

Index out of range when binding a Slider value to a nested array in EnvironmentObject

描述:

我有一个具有以下层次结构的模型:

模型运行良好。我可以添加步骤、参数,并将当前步骤设置为名为 recipe@EnvironmentObject

我创建了一个示例项目 here,其中包含两个步骤和参数列表,以及三个用于在三个硬编码步骤中添加一个步骤的按钮,每个步骤都包含 0、1 的数组, 或 3 个参数。

顶部列表是步骤行,每行都是一个用于填充底部列表的按钮。底部列表是参数列表,每个参数列表包含一个标签和一个 VStack.

中的滑块

一切正常,除非我 (a) 将滑块绑定到我的模型 (b) 列表包含比当前步骤现在更多的滑块(行) .我得到一个 index out of range error

如果我将滑块值绑定到一个局部变量,一切都有效。这是相关代码:

class Recipe: BindableObject {
    var didChange = PassthroughSubject<Void, Never>()
    var currentStep = Step() {
        didSet {
            didChange.send(())
        }
    }
}

struct Parameter: Identifiable {
    var id:Int = 0
    var name = ""
    var minimum:Float = 0
    var maximum:Float = 100
    var `default`:Float = 30
    var current:Float = 30
}

struct StepRow: View {
    @EnvironmentObject var recipe: Recipe
    var step: Step!

    init(step: Step) {
        self.step = step
    }
    var body: some View {
        Button(action: {
            self.setCurrentStep()
        }) {
            HStack {
                Text(step.name).font(Font.body.weight(.bold))
            }.frame(height: 50)
        }
    }
    func setCurrentStep() {
        recipe.currentStep = step
    }
}
struct ParameterRow: View {
    @EnvironmentObject var recipe: Recipe
    @State var sliderValue:Float = 30
    var parameter: Parameter!

    init(parameter: Parameter) {
        self.parameter = parameter
    }

    var body: some View {
        VStack {
            Text(parameter.name)
            Slider(

                // This works, swap these two lines to duplicate the index out of range error by:
                // - Adding step 1, step 2, step 3, and finally step 4
                // - Tapping each step in the step list in order, the first three will work but the last one won't

                //value: $recipe.currentStep.parameters[parameter.id].current,
                value: self.$sliderValue,

                from: parameter.minimum,
                through: parameter.maximum,
                by: 1.0
            )
        }
    }
}
struct ContentView : View {
    @EnvironmentObject var recipe: Recipe
    var body: some View {
        VStack {
            List {
                ForEach(recipe.steps) { step in
                    StepRow(step: step)
                }
            }
            List {
                ForEach(recipe.currentStep.parameters) { parameter in
                    ParameterRow(parameter: parameter)
                }
            }
        }
    }
}

同样,一个工作示例是项目 here

我仍在检查您的代码。但我想评论一下在您的函数 addStepX():

中引起我注意的事情
    func addStep1() {
        let newStep = Step(id: UUID(), name: "Step #1", parameters: [Parameter]())
        currentStep = newStep
        steps.insert(newStep, at: steps.count)
    }

你知道 steps.insert() 不会触发 didSet,所以 didChange.send() 不会执行吗?我建议您颠倒顺序并先插入步骤,然后再更新 currentStep。这样,在完成所有更改后,您只调用 didChange.send() 一次。

    func addStep1() {
        let newStep = Step(id: UUID(), name: "Step #1", parameters: [Parameter]())
        steps.insert(newStep, at: steps.count)
        currentStep = newStep
    }

请注意,更改它仍然不能解决问题,但我还是应该提一下,因为这绝对是个问题。

更新

经过您的更改,代码看起来更清晰了。而且我似乎找到了一种防止 越界 .

的方法

看来问题是时间问题。您的数组已更新,但 List 传递的参数仍然很旧。最终,它会赶上来,但由于越界崩溃......它永远不会。那么为什么不让它成为有条件的呢?

请注意,我还向 Text() 视图添加了滑块值,以表明绑定成功:

struct ParameterRow: View {
    @EnvironmentObject var recipe: Recipe
    @State var sliderValue:Float = 30
    var parameter: Parameter!

    init(parameter: Parameter) {
        self.parameter = parameter
    }

    var body: some View {
        VStack {
            Text("\(parameter.name) = \(parameter.id < recipe.currentStep.parameters.count ? recipe.currentStep.parameters[parameter.id].current : -1)")
            Slider(
                value: parameter.id < recipe.currentStep.parameters.count ? $recipe.currentStep.parameters[parameter.id].current : .constant(0),
                from: parameter.minimum,
                through: parameter.maximum,
                by: 1.0
            )
        }
    }
}