将 Slider 值绑定到 EnvironmentObject 中的嵌套数组时索引超出范围 [英] Index out of range when binding a Slider value to a nested array in EnvironmentObject

查看:16
本文介绍了将 Slider 值绑定到 EnvironmentObject 中的嵌套数组时索引超出范围的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

说明:

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

I have a model that has the following hierarchy:

  • 食谱
  • ...steps(一个数组)
  • ...currentStep
  • ......参数(数组)
  • ........最低
  • .......最大
  • .........默认
  • ........当前

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

The model works well. I can add steps, parameters, and set the current step to an @EnvironmentObject called recipe.

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

I've created a sample project here with I two lists of steps and parameters, along with three buttons to add a single step among three hard-coded ones, each containing an array of 0, 1, or 3 parameters.

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

The top list is the step rows, each being a button to populate the bottom list. The bottom list is the parameter list, each containing a label and a slider in a VStack.

一切正常,除非我 (a) 将滑块绑定到我的模型 (b) 列表包含比当前步骤更多的滑块(行).我收到索引超出范围错误.

All works fine, except when i am (a) binding the slider to my model and (b) the list contains more sliders (row) than the current step now has. I get an index out of range error.

如果我将滑块值绑定到局部变量,则一切正常.相关代码如下:

If I bind the slider value to a local variable, it all works. Here's the relevant code:

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)
                }
            }
        }
    }
}

同样,一个工作示例是项目这里.

Again, a working example of this is project here.

推荐答案

我仍在检查您的代码.但我想评论一下在你的函数 addStepX() 中引起我注意的事情:

I'm still going through your code. But I'd like to comment on something that caught my eye in your functions 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() 不会执行?我建议你颠倒顺序,先插入 step,然后再更新 currentStep.这样你就可以在所有更改完成后调用 didChange.send() 一次.

Are you aware that the steps.insert() will not trigger a didSet, and so the didChange.send() will not execute? I propose you invert the order and first insert the step, and later you update currentStep. This way you call didChange.send() exactly once, after all your changes are done.

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

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

Note that changing that still does not fix the problem, but I though I should mention, as it is definitely a problem.

更改后,代码看起来更简洁了.而且我似乎找到了一种防止越界的方法.

After your changes, the code looks much cleaner. And I seem to have found a way of preventing the out of bounds.

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

It seems the problem is due to a timing issue. Your array gets updated, but the parameter passed by the List is still old. Eventually, it will catch up, but because of the out of bound crash... it never does. So why not make it conditional?

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

Note that I also added the slider value to the Text() view, to make it evident that the binding is successful:

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
            )
        }
    }
}

这篇关于将 Slider 值绑定到 EnvironmentObject 中的嵌套数组时索引超出范围的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆