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

Swiftui 将滑块值绑定到EnvironmentObject中的嵌套数组时索引超出范围,swiftui,combine,Swiftui,Combine,说明: 我有一个具有以下层次结构的模型: 配方 …步骤(数组) …currentStep ……参数(数组) 最低限度 最大值 ……违约 当前 该模型运行良好。我可以添加步骤、参数,并将当前步骤设置为名为配方的@EnvironmentObject 我创建了一个示例项目,其中包含两个步骤和参数列表,以及三个按钮,用于在三个硬编码步骤中添加一个步骤,每个步骤包含一个0、1或3个参数的数组 顶部列表是步骤行,每个行都是填充底部列表的按钮。底部列表是参数列表,每个参数列表包含VStack中的标签和滑块

说明:

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

  • 配方
  • …步骤(数组)
  • currentStep
  • ……参数(数组)
  • 最低限度
  • 最大值
  • ……违约
  • 当前
该模型运行良好。我可以添加步骤、参数,并将当前步骤设置为名为
配方的
@EnvironmentObject

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

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

除了(a)将滑块绑定到我的模型(b)列表中包含的滑块(行)比当前步骤中包含的滑块(行)更多时,所有操作都正常。我得到一个
索引超出范围错误

如果我将滑块值绑定到一个局部变量,那么一切都可以正常工作。以下是相关代码:

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)
                }
            }
        }
    }
}
类配方:BindableObject{
var didChange=PassthroughSubject()
var currentStep=步骤(){
迪塞特{
didChange.send(())
}
}
}
结构参数:可识别{
变量id:Int=0
var name=“”
变量最小值:浮点=0
var最大值:浮动=100
var`default`:Float=30
无功电流:浮动=30
}
结构StepRow:视图{
@环境对象变量配方:配方
步骤:步骤!
初始化(步骤:步骤){
self.step=step
}
var body:一些观点{
按钮(操作:{
self.setCurrentStep()
}) {
HStack{
文本(步骤名称)。字体(字体。正文。重量(.bold))
}.框架(高度:50)
}
}
func setCurrentStep(){
recipe.currentStep=步骤
}
}
结构参数Row:视图{
@环境对象变量配方:配方
@状态变量sliderValue:Float=30
var参数:参数!
init(参数:参数){
self.parameter=参数
}
var body:一些观点{
VStack{
文本(parameter.name)
滑块(
//这样,通过以下方式交换这两行以复制索引超出范围错误:
//-添加步骤1、步骤2、步骤3,最后添加步骤4
//-按顺序轻敲步骤列表中的每个步骤,前三个步骤可以工作,但最后一个步骤不行
//值:$recipe.currentStep.parameters[parameter.id].current,
值:self.$sliderValue,
from:parameter.minimum,
通过:parameter.max,
作者:1.0
)
}
}
}
结构ContentView:View{
@环境对象变量配方:配方
var body:一些观点{
VStack{
名单{
ForEach(recipe.steps){步进
StepRow(步骤:步骤)
}
}
名单{
ForEach(recipe.currentStep.parameters){中的参数
ParameterRow(参数:参数)
}
}
}
}
}

同样,这方面的一个工作示例是project。

我仍在浏览您的代码。但我想谈谈在函数addStepX()中引起我注意的一些问题:

func addStep1(){
让newStep=Step(id:UUID(),名称:“Step#1”,参数:[Parameter]())
currentStep=newStep
steps.insert(newStep,at:steps.count)
}
您知道steps.insert()不会触发didSet,因此不会执行didChange.send()?我建议您颠倒顺序,首先插入步骤,然后更新currentStep。这样,在完成所有更改后,只需调用一次didChange.send()

func addStep1(){
让newStep=Step(id:UUID(),名称:“Step#1”,参数:[Parameter]())
steps.insert(newStep,at:steps.count)
currentStep=newStep
}
请注意,改变这一点仍然不能解决问题,但我想我应该提到,因为这肯定是一个问题

更新 更改后,代码看起来更干净。我似乎找到了一种防止出界的方法

问题似乎是由于时间问题。数组已更新,但列表传递的参数仍然旧。最终,它将迎头赶上,但由于失控的崩溃。。。从来没有。那么为什么不让它有条件呢

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

struct参数row:View{
@环境对象变量配方:配方
@状态变量sliderValue:Float=30
var参数:参数!
init(参数:参数){
self.parameter=参数
}
var body:一些观点{
VStack{
文本(“\(parameter.name)=\(parameter.id
我仍在考虑你的问题。但只是一个简短的评论。可以使用默认值,只需在定义中使用反向引号即可。我在SwiftUI声明文件中看到苹果一直在这样做:
var`default`:String
。然后,您可以使用不带引号的变量。谢谢您的提示!我一定会实现它-t