SwiftUI 中互连的多个滑块
Posted
技术标签:
【中文标题】SwiftUI 中互连的多个滑块【英文标题】:Interlinked Multiple Sliders in SwiftUI 【发布时间】:2020-02-04 14:57:57 【问题描述】:我想知道是否有人可以帮助我。
在我正在开发的应用程序中,我需要将一个值分成四个不同的区域,我想通过四个滑块来实现,这些滑块控制每个区域的百分比。
对于每个Slider
,我创建了一个@State private var sliderAValue = 25
并通过Slider(value: $sliderAValue, in: 0...100)
将它们绑定到滑块
显然,当我更改任何滑块的值时,其他值也应更改,以使总百分比始终为 100。
但更重要的是,其他三个值之间的比率应该是相同的:例如。如果原来的分割是 30 : 30 : 20 : 20 并且用户将第三个元素更改为 40%,那么新的分割应该变成 22.5 : 22.5 : 40 : 15。
在State
变量上应用didSet
或willSet
似乎没有任何效果,但我显然需要将原始比率存储在某处并自动更新它们。
有什么想法吗?
【问题讨论】:
由于状态是Int
类型,在您的示例中使用 22.5 会发生什么?如果我们将 22.5 都转换为 22,则总和不会是 100。
抱歉,@Qbyte,我没有说清楚。存储的值是 Double
值,但在 UI 中四舍五入为 Int
如果用户将一个滑块拖动到 100 会发生什么情况,所以我们有一个 100 : 0 : 0 : 0 的分割然后到 0?应该是 0 : 33 : 33 : 33 ?
谢谢@Qbyte,这看起来是一个很好的答案。
认为我可以显示到小数点后 1 位 - 这不会消除您指出的问题,但会减少其影响。
【参考方案1】:
您可以将所有State
变量的绑定存储在Array<Binding<Double>>
类型的数组中。如果您愿意,这允许您添加更多滑块而无需更多代码。
struct Sliders: View
@State var value1 = 100.0
@State var value2 = 0.0
@State var value3 = 0.0
@State var value4 = 0.0
var body: some View
// add all bindings which you want to synchronize
let allBindings = [$value1, $value2, $value3, $value4]
return VStack
Button(action:
// Manually setting the values does not change the values such
// that they sum to 100. Use separate algorithm for this
self.value1 = 10
self.value2 = 40
)
Text("Test")
Text("\(value1)")
synchronizedSlider(from: allBindings, index: 0)
Text("\(value2)")
synchronizedSlider(from: allBindings, index: 1)
Text("\(value3)")
synchronizedSlider(from: allBindings, index: 2)
Text("\(value4)")
synchronizedSlider(from: allBindings, index: 3)
.padding()
func synchronizedSlider(from bindings: [Binding<Double>], index: Int) -> some View
return Slider(value: synchronizedBinding(from: bindings, index: index),
in: 0...100)
func synchronizedBinding(from bindings: [Binding<Double>], index: Int) -> Binding<Double>
return Binding(get:
return bindings[index].wrappedValue
, set: newValue in
let sum = bindings.indices.lazy.filter $0 != index .map bindings[$0].wrappedValue .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.indices
if i != index
bindings[i].wrappedValue = bindings[i].wrappedValue * remaining / sum
else
// handle 0 sum
let newOtherValue = remaining / Double(bindings.count - 1)
for i in bindings.indices
if i != index
bindings[i].wrappedValue = newOtherValue
bindings[index].wrappedValue = newValue
)
如果您只想同步value1
/value2
和value3
/value4
的滑块,您可以将body
中的代码更改为:
let bindings12 = [$value1, $value2]
let bindings34 = [$value3, $value4]
return VStack
Text("\(value1)")
synchronizedSlider(from: bindings12, index: 0)
Text("\(value2)")
synchronizedSlider(from: bindings12, index: 1)
Text("\(value3)")
synchronizedSlider(from: bindings34, index: 0)
Text("\(value4)")
synchronizedSlider(from: bindings34, index: 1)
.padding()
【讨论】:
【参考方案2】:这是一个使用 onEditingChanged 的解决方案。在您释放正在编辑的滑块之前,其他滑块不会更新。 (当然必须有一种方法可以将计算提取到一个函数中,这样它就不会重复四次,但它现在不会来找我。)
struct ContentView: View
@State private var sliderAValue: Double = 25
@State private var sliderBValue: Double = 25
@State private var sliderCValue: Double = 25
@State private var sliderDValue: Double = 25
var body: some View
VStack
// Slider A
Slider(value: $sliderAValue, in: 0...100, onEditingChanged: _ in
let remaining = self.sliderBValue + self.sliderCValue + self.sliderDValue
let percentageB = self.sliderBValue / remaining
let percentageC = self.sliderCValue / remaining
let percentageD = self.sliderDValue / remaining
let available = 100 - self.sliderAValue
self.sliderBValue = available * percentageB
self.sliderCValue = available * percentageC
self.sliderDValue = available * percentageD
)
Text("Slider A: \(sliderAValue, specifier: "%.1f")")
// Slider B
Slider(value: $sliderBValue, in: 0...100, onEditingChanged: _ in
let remaining = self.sliderAValue + self.sliderCValue + self.sliderDValue
let percentageA = self.sliderAValue / remaining
let percentageC = self.sliderCValue / remaining
let percentageD = self.sliderDValue / remaining
let available = 100 - self.sliderBValue
self.sliderAValue = available * percentageA
self.sliderCValue = available * percentageC
self.sliderDValue = available * percentageD
)
Text("Slider B: \(sliderBValue, specifier: "%.1f")")
// Slider C
Slider(value: $sliderCValue, in: 0...100, onEditingChanged: _ in
let remaining = self.sliderAValue + self.sliderBValue + self.sliderDValue
let percentageA = self.sliderAValue / remaining
let percentageB = self.sliderBValue / remaining
let percentageD = self.sliderDValue / remaining
let available = 100 - self.sliderCValue
self.sliderAValue = available * percentageA
self.sliderBValue = available * percentageB
self.sliderDValue = available * percentageD
)
Text("Slider C: \(sliderCValue, specifier: "%.1f")")
// Slider D
Slider(value: $sliderDValue, in: 0...100, onEditingChanged: _ in
let remaining = self.sliderAValue + self.sliderBValue + self.sliderCValue
let percentageA = self.sliderAValue / remaining
let percentageB = self.sliderBValue / remaining
let percentageC = self.sliderCValue / remaining
let available = 100 - self.sliderDValue
self.sliderAValue = available * percentageA
self.sliderBValue = available * percentageB
self.sliderCValue = available * percentageC
)
Text("Slider D: \(sliderDValue, specifier: "%.1f")")
【讨论】:
以上是关于SwiftUI 中互连的多个滑块的主要内容,如果未能解决你的问题,请参考以下文章