简体   繁体   English

SwiftUI:如何调整圆形滑块/选择器

[英]SwiftUI: How can I adjust the circle Slider/Picker

I would like to enable values to be entered using a Circle Slider.我想启用使用圆形滑块输入的值。 Before that I had implemented the Input of values with a Picker.在此之前,我已经使用 Picker 实现了值的输入。 But now I have a little Problem with the Circle Slider.但是现在我对圆形滑块有一个小问题。 I want to adjust the values that can be selected.我想调整可以选择的值。 Now I can select values from 0% to 100%.现在我可以选择从 0% 到 100% 的值。 How can I display the values from 21% to 90% but from 50% the values should appear in „10 steps“.如何显示从 21% 到 90% 的值,但从 50% 开始,这些值应该出现在“10 个步骤”中。 (21%,22%,23%… 50%, 60%, 70%, 80%, 90%) Is it possible to set the starting value to 32? (21%,22%,23%... 50%, 60%, 70%, 80%, 90%) 是否可以将起始值设置为 32?

The Code from the new Circle Slider:来自新圆形滑块的代码:

struct Slider: View {
    
    @State var size = UIScreen.main.bounds.width - 100
    @State var progress: CGFloat = 0
    @State var angle: Double = 0
    
    var body: some View {
        VStack {
            ZStack {
                Circle()
                    .stroke(Color("CircleSlider"), style: StrokeStyle(lineWidth: 55, lineCap: .round, lineJoin: .round))
                    .frame(width: size, height: size)
                
                Circle()
                    .trim(from: 0, to: progress)
                    .stroke(Color("CircleSliderfill"), style: StrokeStyle(lineWidth: 55, lineCap: .butt))
                    .frame(width: size, height: size)
                    .rotationEffect(.init(degrees: -90))
                
                Circle()
                    .fill(Color("CircleSlider"))
                    .frame(width: 55, height: 55)
                    .offset(x: size / 2)
                    .rotationEffect(.init(degrees: -90))
                
                Circle()
                    .fill(Color.white)
                    .frame(width: 55, height: 55)
                    .offset(x: size / 2)
                    .rotationEffect(.init(degrees: angle))
                    .gesture(DragGesture().onChanged(onDrag(value:)))
                    .rotationEffect(.init(degrees: -90))
                
                //example from 0 to 100%
                Text(String(format: "%.0f", progress * 100) + "%")
            }
        }
    }
    
    func onDrag(value: DragGesture.Value) {
        let vector = CGVector(dx: value.location.x, dy: value.location.y)
        let radians = atan2(vector.dy - 27.5, vector.dx - 27.5
        var angle = radians * 180 / .pi
        
        if angle < 0 {
            angle = 360 + angle
        }
        
        withAnimation(Animation.linear(duration: 0.15)) {
            
            let progress = angle / 360
            self.progress = progress
            self.angle = Double(angle)
        }
    }
}

The Code from the old Picker:来自旧 Picker 的代码:

struct ValueO2: View {
    @State var valueIndexO2MOD_0 = 32
    @State var valueArray : [Int] = []
    
    var body: some View {
        VStack{
            Section {
                Text("O2"))
                Picker("",selection: $valueIndexO2MOD_0) {
                    ForEach(valueArray, id: \.self){ value in
                        Text("\(value) %")
                    }
                }
            }
            .labelsHidden()
        }.onAppear{
            self.initPickerIndex()
        }
    }
           func initPickerIndex(){
               
               valueArray = []
               
               for index1 in 21..<50 {
                   valueArray.append(index1)
               }
                          
               for index2 in 1...5{
                   valueArray.append(40 + index2 * 10)
               }
           }
}

You can do that with some simple modifications:您可以通过一些简单的修改来做到这一点:

If you want to change the Text in "10 steps" you can do this:如果您想在“10 个步骤”中更改文本,您可以这样做:

Text(String(format: "%.0f", progress >= 0.5 ? round(progress * 10) * 10 : progress * 100) + "%")

To change the actual value in "10 steps", you need to change the calculation of your progress and angle slightly:要更改“10步”中的实际值,您需要稍微更改进度和角度的计算:

func onDrag(value: DragGesture.Value) {
    let vector = CGVector(dx: value.location.x, dy: value.location.y)
    let radians = atan2(vector.dy - 27.5, vector.dx - 27.5)
    var angle = radians * 180 / .pi
    
    if angle < 0 {
        angle = 360 + angle
    }
    
    var progress = angle / 360
    
    if progress >= 0.5 {
        // Round & update angle
        progress = round(progress * 10) / 10
        angle = progress * 360
    }
    
    withAnimation(Animation.linear(duration: 0.15)) {
        self.progress = progress
        self.angle = Double(angle)
    }
}

To set a start value you can use a simple function:要设置起始值,您可以使用一个简单的函数:

func setStartValue(value: CGFloat) {
    withAnimation(Animation.linear(duration: 0.15)) {
        progress = value
        angle = Double(progress * 360)
    }
}

You can call it when the view appears by using the .onAppear modifier:您可以使用.onAppear修饰符在视图出现时调用它:

VStack {
        // your slider
    }.onAppear {
        setStartValue(value: 0.32)
    }

EDIT: Limit range from 21% to 90%编辑:限制范围从 21% 到 90%

func onDrag(value: DragGesture.Value) {
    let vector = CGVector(dx: value.location.x, dy: value.location.y)
    let radians = atan2(vector.dy - 27.5, vector.dx - 27.5)
    var angle = radians * 180 / .pi
    
    if angle < 0 {
        angle = 360 + angle
    }
    
    var progress = angle / 360
    
    // "10 Steps"
    if progress >= 0.5 {
        // Round & update angle
        progress = round(progress * 10) / 10
        angle = progress * 360
    }
    
    // Boundaries (21% to 90%)
    let minValue: CGFloat = 0.21
    let maxValue: CGFloat = 0.9
    
    if progress < minValue {
        progress = minValue
        angle = progress * 360
    } else if progress > maxValue {
        progress = maxValue
        angle = progress * 360
    }
    
    withAnimation(Animation.linear(duration: 0.15)) {
        self.progress = progress
        self.angle = Double(angle)
    }
}

I think that the idea of selecting elements from an array using the slider is interesting.我认为使用滑块从数组中选择元素的想法很有趣。 So here is a second answer how this can be achieved:所以这是如何实现的第二个答案:

    struct CircularSlider: View {
    
    @State var size = UIScreen.main.bounds.width - 100
    @State var progress: CGFloat = 0
    @State var angle: Double = 0
    
    let validValues = Array(21...90) // [1.2, 1.3, 1.4, 1.5, 1.6]
    @State var selectedValue: Double = 31
    
    var body: some View {
        VStack {
            ZStack {
                Circle()
                    .stroke(Color(.lightGray), style: StrokeStyle(lineWidth: 55, lineCap: .round, lineJoin: .round))
                    .frame(width: size, height: size)
                
                Circle()
                    .trim(from: 0, to: progress)
                    .stroke(Color(.systemGreen), style: StrokeStyle(lineWidth: 55, lineCap: .butt))
                    .frame(width: size, height: size)
                    .rotationEffect(.init(degrees: -90))
                
                Circle()
                    .fill(Color(.lightGray))
                    .frame(width: 55, height: 55)
                    .offset(x: size / 2)
                    .rotationEffect(.init(degrees: -90))
                
                Circle()
                    .fill(Color.white)
                    .frame(width: 55, height: 55)
                    .offset(x: size / 2)
                    .rotationEffect(.init(degrees: angle))
                    .gesture(DragGesture().onChanged(onDrag(value:)))
                    .rotationEffect(.init(degrees: -90))
                
                // Text(String(format: "%.1f", selectedValue))
                Text("\(selectedValue)")
                
            }
        }.onAppear {
            setStartValue(value: selectedValue)
        }
    }
    
    private func onDrag(value: DragGesture.Value) {
        let vector = CGVector(dx: value.location.x, dy: value.location.y)
        let radians = atan2(vector.dy - 27.5, vector.dx - 27.5)
        var angle = radians * 180 / .pi
        
        if angle < 0 {
            angle = 360 + angle
        }
        
        let progress = angle / 360
        
        // Select Value in Array
        selectValue(progress: progress)
        
        withAnimation(Animation.linear(duration: 0.15)) {
            self.progress = progress
            self.angle = Double(angle)
        }
    }
    
    private func selectValue(progress: CGFloat) {
        let segments: CGFloat = CGFloat(validValues.count - 1)
        let indexLimit: CGFloat = 100 / segments
        let index = Int(round((progress / indexLimit) * 100))
        selectedValue = Double(validValues[index])
    }
    
    private func setStartValue(value: Double) {
        
        // Transform value to progress
        if let index = validValues.firstIndex(of: Int(value)) {
            progress = CGFloat(index) / CGFloat(validValues.count)
            print(progress)
            
            withAnimation(Animation.linear(duration: 0.15)) {
                self.progress = CGFloat(progress)
                self.angle = Double(self.progress * 360)
            }
        }
        
    }
    
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM