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 变量上应用didSetwillSet 似乎没有任何效果,但我显然需要将原始比率存储在某处并自动更新它们。

有什么想法吗?

【问题讨论】:

由于状态是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/value2value3/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 中互连的多个滑块的主要内容,如果未能解决你的问题,请参考以下文章

DragGesture 块在 SwiftUI 中触摸滑块

在 SwiftUI 中使用滑块创建音频播放器单元格

您将如何在 swiftUI/Swift 的滑块轨道中绘制节点?

如何使用 SwiftUI 拖动工作滑块

如何在 SwiftUI 中停止快速移动视图的频闪

具有最大值的 SwiftUI 自定义滑块