使@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
}
从概念上讲,我似乎需要做两件事来调整链接代码:
- 生成一个Binding数组,元素个数等于Items.Component.EndIndex和
- 将每个 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
})
}
}
我是编程和探索 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
}
从概念上讲,我似乎需要做两件事来调整链接代码:
- 生成一个Binding数组,元素个数等于Items.Component.EndIndex和
- 将每个 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
})
}
}