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

查看:34
本文介绍了将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)列表包含的滑块(行)比当前步骤要多.我得到 index超出范围错误.

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

同样,项目此处.

推荐答案

我仍在检查您的代码.但我想评论一下您的函数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()不会执行?我建议您反转顺序并首先插入步骤,然后再更新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天全站免登陆