swift 圆形滑块-在360度处停止旋转

wooyq4lh  于 2023-05-05  发布在  Swift
关注(0)|答案(1)|浏览(210)

我有一个滑块,我想停止旋转后360度。我的目标是,一旦用户360度点击,我将为他们显示一个自定义文本选项。
我现在遇到的问题是它停在我的指定值(在这种情况下,我只是设置滑块停止在200),但发生的事情是它过去的指定值。例如,它跳到203、259等。它没有精确地停在值上。
任何建议将是伟大的!

struct CaloriesCircularSelector: View {
    @State var calorieValue: CGFloat = 0.0
    @State var angleValue: CGFloat = 0.0
    @State private var isDragging = false
   @State private var stopGesture = false
    @State var prevCalorieValue: CGFloat = 0.0
    
    @State var movingForward = false
    @State var movingBackwards = false
    
    let config = ConfigCircular(
                minimumValue: 0.0,
                maximumValue: 999.0,
                totalValue: 1000.0,
                knobRadius: 15.0,
                radius: 125.0)
    var body: some View {
        ZStack {
            
            Circle()
                .fill(.red)
                .frame(width: config.radius * 2, height: config.radius * 2)
                .scaleEffect(1.2)
             
          
            
            Circle()
                .trim(from: 0.0, to: calorieValue/config.totalValue)
                .stroke(Color.blue, lineWidth: 15)
                .frame(width: config.radius * 2, height: config.radius * 2)
                .rotationEffect(.degrees(-90))
            
            Circle()
                .fill(isDragging ? Color.red : Color.blue)
                .frame(width: config.knobRadius * 2, height: config.knobRadius * 2)
                .padding(10)
                .offset(y: -config.radius)
                .rotationEffect(Angle.degrees(Double(angleValue)))
                .gesture(
                   DragGesture(minimumDistance: 0.2)
                       .onChanged { value in
                           isDragging = true // Set isDragging to true when the user starts dragging
                         
                           
                       }
                       .onEnded { _ in
                           isDragging = false // Set isDragging to false when the user stops dragging
                       }
               )
            
            Text("\(String.init(format: "%.0f", calorieValue)) cals")
                            .font(.system(size: 50))
                            .foregroundColor(.white)
                            
        }
        .onChange(of: self.calorieValue) { newValue in
                   
            
            // Check if the user is moving forward or backward, can't find a use for this right now
                       if newValue > prevCalorieValue {
                           movingForward = true
                           movingBackwards = false
                           print("Moving forward")
                       } else if newValue < prevCalorieValue {
                           movingForward = false
                           movingBackwards = true
                           print("Moving backward")
                       }
                       prevCalorieValue = newValue
                }
        
    }
    
    private func change(location: CGPoint) {
        // creating vector from location point
        let vector = CGVector(dx: location.x, dy: location.y)
    
        let angle = atan2(vector.dy - (config.knobRadius + 10), vector.dx - (config.knobRadius + 10)) + .pi/2.0
        
        let fixedAngle = angle < 0.0 ? angle + 2.0 * .pi : angle
       
        let value = fixedAngle / (2.0 * .pi) * config.totalValue
       if value >= config.minimumValue && value <= config.maximumValue {
           if angleValue >= 200 && value > calorieValue {
               // stop increasing the calorie value if it is already 200 or greater
               return
           }

           calorieValue = value
           angleValue = fixedAngle * 180 / .pi
       }
       
    }
}

struct ConfigCircular {
    let minimumValue: CGFloat
    let maximumValue: CGFloat
    let totalValue: CGFloat
    let knobRadius: CGFloat
    let radius: CGFloat
}

t3psigkw

t3psigkw1#

您必须使用.onChange观察calorieValue中的更改。下面是如何做到这一点。

struct CaloriesCircularSelector: View {
    @State var calorieValue: CGFloat = 0.0
    @State var angleValue: CGFloat = 0.0
    @State private var isDragging = false
    @State private var stopGesture = false

    @State private var showSlider: Bool = true // Added a new boolean to control when to show the slider

    let config = ConfigCircular(
                minimumValue: 0.0,
                maximumValue: 999.0,
                totalValue: 1000.0,
                knobRadius: 15.0,
                radius: 125.0)

    var body: some View {
        ZStack {
            Circle()
                .frame(width: config.radius * 2, height: config.radius * 2)
                .scaleEffect(1.2)

            if(showSlider) { // Conditionally show or hide the circles responsible for the slider
                Circle()
                    .trim(from: 0.0, to: calorieValue/config.totalValue)
                    .stroke(Color.blue, lineWidth: 15)
                    .frame(width: config.radius * 2, height: config.radius * 2)
                    .rotationEffect(.degrees(-90))

                Circle()
                    .fill(isDragging ? Color.red : Color.blue)
                    .frame(width: config.knobRadius * 2, height: config.knobRadius * 2)
                    .padding(10)
                    .offset(y: -config.radius)
                    .rotationEffect(Angle.degrees(Double(angleValue)))
                    .gesture(
                        DragGesture(minimumDistance: 0.2)
                            .onChanged { value in
                                // THIS IS VERY IMPORTANT, ONLY call `change` IF AND IF self.calorieValue is less than 360.
                                if !stopGesture && self.calorieValue < 360 {
                                isDragging = true // Set isDragging to true when the user starts dragging
                                    change(location: value.location) // << controls movement of knob
                                }

                            }
                            .onEnded { _ in
                                isDragging = false // Set isDragging to false when the user stops dragging
                            }
                    )
            }

            // This is really up to you but I decided to add the textfield above the current text that shows the calorieValue 
            VStack(spacing: 10) 
                // Pay attention I am using the variable `showSlider`
                if(!showSlider) {
                    TextField("Enter Calorie", value: self.$calorieValue, formatter: NumberFormatter())
                        .textFieldStyle(.roundedBorder)
                        .border(Color.green)
                }

                Text("\(String.init(format: "%.0f", calorieValue)) cals")
                    .font(.system(size: 50))
                    .foregroundColor(.white)
            }
        }
        // Finally, this is the important bit that will define when to show the textfield and when to hide it and show the slider
        .onChange(of: self.calorieValue) { newValue in
            if newValue >= 360 && self.showSlider == true {
                self.showSlider = false
                self.isDragging = false
                self.stopGesture = true
            } else if(newValue < 360 && self.showSlider == false) {
                self.showSlider = true
                self.stopGesture = false
            }
        }
    }

    private func change(location: CGPoint) {
        // creating vector from location point
        let vector = CGVector(dx: location.x, dy: location.y)

        let angle = atan2(vector.dy - (config.knobRadius + 10), vector.dx - (config.knobRadius + 10)) + .pi/2.0

        let fixedAngle = angle < 0.0 ? angle + 2.0 * .pi : angle

        let value = fixedAngle / (2.0 * .pi) * config.totalValue

        if value >= config.minimumValue && value <= config.maximumValue {
            if angleValue >= 359 {
                stopGesture = true
            }
            else{
                stopGesture = false
            }
            calorieValue = value
            angleValue = fixedAngle * 180 / .pi
          }

    }
}

struct ConfigCircular {
    let minimumValue: CGFloat
    let maximumValue: CGFloat
    let totalValue: CGFloat
    let knobRadius: CGFloat
    let radius: CGFloat
}

请阅读代码中的注解,因为它们将帮助您并指导您完成它。还要注意的是,这段代码并不完美,因为当你在textfield中输入任何值时,如果值低于360,它就会显示滑块。因为我不确定最终的行为,所以这是留给你去弄清楚你希望它是什么样子的。
还请注意,如果我是你,我会打破我的看法成subviews例如我会做

...
var slider: some View {
// Put the slider code here
}
...

最后,您可能需要添加一些动画。您可以在.onChange内部使用withAnimation()执行此操作,也可以直接在要设置动画的视图上调用.animation(.linear, value: self.showSlider)

相关问题