使@State变量和UI适应SwiftUI中的用户操作

Adapting @State variables and UI to user actions in SwiftUI

我是编程和探索 SwiftUI 方面的新手。我一直在应对一个挑战太久了,希望有人能指导我正确的方向!

我想要一个相互关联的滑块列表(如 中所示),但滑块的数量会根据用户采取的操作动态变化。

例如,用户可以选择各种项目,然后使用滑块调整百分比变量(并且这些百分比在链接示例中是相互依赖的)。

class Items: ObservableObject {
    
    @Published var components = [ItemComponent]()
    
    func add(component: itemComponent){
        components.append(component)
        }

}

struct ItemComponent: Hashable, Equatable, Identifiable {
       
    var id = UUID().uuidString
    var name: String = ""
    var percentage: Double
}

从概念上讲,我似乎需要做两件事来调整链接代码:

  1. 生成一个Binding数组,元素个数等于Items.Component.EndIndex和
  2. 将每个 Binding 分配给每个 ItemComponent 的百分比。

两个我都摸不着头脑。对于 1.,我可以轻松地手动创建任意数量的变量,例如

@State var value1 = 100
@State var value2 = 100
@State var value3 = 100

let allBindings = [$value1, $value2, $value3]

但是如何自动生成它们?

对于 2.,我可以使用 ForEach() 来调用组件或索引,但不能同时调用两者:

ForEach(Items.components){ component in 
    Text("\(component.name)")
    Text("\(component.percentage)")
}

ForEach(Items.components.indices){ i in 
    synchronizedSlider(from: allBindings, index: i+1)
}

在损坏的代码中,我想要的是这样的:

ForEach(Items.component){component in
     HStack{
            Text("component.name")
Spacer()
synchronizedSlider(from: allBindings[$component.percentage], index: component.indexPosition)

      }


其中 allBindings[$component.percentage] 是由每个 itemComponent 的百分比组成的绑定数组,index 是 itemComponent 的索引。

如果相关,我很乐意分享更多代码。任何帮助将不胜感激!

要调整您链接的现有代码,如果您要有动态数量的滑块,您肯定希望您的 @State 是一个数组,而不是单个 @State变量,必须进行硬编码。

一旦你有了它,就会有一些小的语法问题将 synchronizedBinding 函数更改为接受 Binding<[ItemComponent]> 而不是 [Binding<Double>],但它们非常小。幸运的是,现有代码在初始硬编码状态之外非常健壮,因此无需任何额外的数学运算。

我正在使用 ItemComponent 而不仅仅是 Double,因为您的示例代码包含它,并且拥有一个具有独特 id 的模型使得 ForEach 代码我' m 用于更易于处理的滑块,因为它需要唯一可识别的项目。


struct ItemComponent: Hashable, Equatable, Identifiable {
    
    var id = UUID().uuidString
    var name: String = ""
    var percentage: Double
}

struct Sliders: View {
    
    @State var values : [ItemComponent] = [.init(name: "First", percentage: 100.0),.init(name: "Second", percentage: 0.0),.init(name: "Third", percentage: 0.0),.init(name:"Fourth", percentage: 0.0),]
    
    var body: some View {
        VStack {
            Button(action: {
                // Manually setting the values does not change the values such
                // that they sum to 100. Use separate algorithm for this
                self.values[0].percentage = 40
                self.values[1].percentage = 60
            }) {
                Text("Test")
            }
            
            Button(action: {
                self.values.append(ItemComponent(percentage: 0.0))
            }) {
                Text("Add slider")
            }
            
            Divider()
            
            ScrollView {
                ForEach(Array(values.enumerated()),id: \.1.id) { (index,value) in
                    Text(value.name)
                    Text("\(value.percentage)")
                    synchronizedSlider(from: $values, index: index)
                }
            }
            
        }.padding()
    }
    
    
    func synchronizedSlider(from bindings: Binding<[ItemComponent]>, index: Int) -> some View {
        return Slider(value: synchronizedBinding(from: bindings, index: index),
                      in: 0...100)
    }
    
    func synchronizedBinding(from bindings: Binding<[ItemComponent]>, index: Int) -> Binding<Double> {
        
        return Binding(get: {
            return bindings[index].wrappedValue.percentage
        }, set: { newValue in
            
            let sum = bindings.wrappedValue.indices.lazy.filter{ [=10=] != index }.map{ bindings[[=10=]].wrappedValue.percentage }.reduce(0.0, +)
            // Use the 'sum' below if you initially provide values which sum to 100
            // and if you do not set the state in code (e.g. click the button)
            //let sum = 100.0 - bindings[index].wrappedValue
            
            let remaining = 100.0 - newValue
            
            if sum != 0.0 {
                for i in bindings.wrappedValue.indices {
                    if i != index {
                        bindings.wrappedValue[i].percentage = bindings.wrappedValue[i].percentage * remaining / sum
                    }
                }
            } else {
                // handle 0 sum
                let newOtherValue = remaining / Double(bindings.wrappedValue.count - 1)
                for i in bindings.wrappedValue.indices {
                    if i != index {
                        bindings[i].wrappedValue.percentage = newOtherValue
                    }
                }
            }
            bindings[index].wrappedValue.percentage = newValue
        })
    }
}