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

Posted

技术标签:

【中文标题】将 Slider 值绑定到 EnvironmentObject 中的嵌套数组时,索引超出范围【英文标题】:Index out of range when binding a Slider value to a nested array in EnvironmentObject 【发布时间】:2019-11-22 07:08:08 【问题描述】:

说明:

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

食谱 ...步骤(一个数组) ...当前步骤 ......参数(一个数组) …………最低 .........最大 .........默认 .........当前

该模型运行良好。我可以添加步骤、参数,并将当前步骤设置为名为@9​​87654325@ 的@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 就是一个工作示例。

【问题讨论】:

我还在处理你的问题。但只是一个快速的评论。您可以使用默认值,您只需在定义中使用反引号。我一直在 SwiftUI 声明文件中看到 Apple 这样做:var `default`: String。然后你可以使用不带引号的变量。 感谢您的提示!我一定会实现它 - 这是一个CoreImage 应用程序,这是更正确的使用方式。 @kontiki,我确实有一个本地示例项目。我可能需要 30 分钟来清理一些东西并配合我所有的尝试。这将是我在 Xcode 13 中第一次使用 GitHub。如果需要,请告诉我。 那太好了。我的代码比英语更流利;-) @kontiki,上传到github.com/justdfd/ListBug我也会更新问题。 【参考方案1】:

我仍在检查您的代码。但我想评论一下在您的函数 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
            )
        
    

【讨论】:

这可能是索引越界错误方面的问题。 (它仍然没有正确绑定。最终我需要将两个列表联系在一起 - 有一个步骤行设置当前步骤 - 并且想在“主”应用程序中一次处理一件事。我有一个想法如何更改设置位置currentStep。我会看看这是否有助于消除第一个问题。感谢您的注意,我同意,这并不意味着我正确绑定了滑块。 快到了。我现在可以绑定到我的模型(索引超出范围错误)在本地绑定并且没有索引超出范围错误。你让我在正确的道路上移动currentStep 的更新。我将更新我的问题和示例项目。 我更新了我的答案,并进行了修复。让我知道它是否有效。 它有效,现在我需要了解一些细节。我是在反应式环境中工作的新手,更不用说Compose,但我确实有时间。查看您的代码,您是否使用Text 中的条件来检查名称(并且需要我初始化为空字符串)?至于滑块,您是否建议 UI 更新得如此之快,实际上在列表中显示了错误的参数,但条件有助于使其如此之快以至于人眼看不到它? :-) 最后,感谢您,您认为这是我可以做得更好的事情吗?这些条件似乎有点不直观。 不客气。我确实认为您的代码应该可以工作。时间问题,我认为这是一个错误,而我的解决方案是一种解决方法。但我不能确定这种行为是否真的是一个错误,或者它是否按预期工作。文本中的条件更相同。这是为了防止越界,毕竟我们访问的是同一个元素。您对视觉更新是正确的。我不知道它是不是太快以至于我们看不到它,还是它只是一些永远无法绘制的内部逻辑。无论如何,事实证明它太快了,不会造成麻烦。最后的建议...(续)。

以上是关于将 Slider 值绑定到 EnvironmentObject 中的嵌套数组时,索引超出范围的主要内容,如果未能解决你的问题,请参考以下文章

单个 Slider 的 Swiftui 多重绑定

如何将多个观察者绑定到一个 ControlProperty

jquery UI Slider绑定到选择

将 Slider 小部件中的值传递到 firestore 数据库

jQuery Round Slider 将速记值添加到范围

绑定时如何在xaml中进行计算?