如何解决使@State 变量和 UI 适应 SwiftUI 中的用户操作
我是编程和探索 SwiftUI 的新手。我一直在应对挑战,希望有人能指引我走向正确的方向!
我想要一个相互链接的滑块列表(如 Interlinked Multiple Sliders in 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.,我可以轻松地手动创建任意数量的变量,例如
@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 的百分比组成的绑定数组,索引是 itemComponent 的索引。
解决方法
为了适应您链接的现有代码,如果您要拥有动态数量的滑块,您肯定希望您的 @State
是一个数组,而不是单个 @State
变量,这必须是硬编码的。
一旦你有了它,就会有一些小的语法问题将 synchronizedBinding
函数更改为接受 Binding<[ItemComponent]>
而不是 [Binding<Double>]
,但它们非常小。幸运的是,现有代码在初始硬编码状态之外非常健壮,因此无需任何额外的数学运算来进行计算。
我正在使用 ItemComponent
而不仅仅是 Double
因为您的示例代码包含它并且具有具有唯一 id
的模型使我正在使用的 ForEach
代码滑块更容易处理,因为它需要唯一可识别的项目。
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",.init(name:"Fourth",]
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{ $0 != index }.map{ bindings[$0].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
})
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。