admin管理员组

文章数量:1122832

I created a simple slider that controls the position of a red circle on the screen within a predefined area. Moving the circle on the screen also moves the slider knob. However, the slider knob has a limited range of movement, from -1.0 to 1.0. When the circle crosses the blue border, the slider logic prevents the knob from sliding further to match the circle's new position.

I’ve been struggling with this issue for days and have tried different approaches, but none have met my requirements. I need to know the logical way (or ways) to handle situations like this, where the slider reaches its maximum range and cannot respond to further movement. How can this issue be resolved in a way that addresses the exhausted slider position rather than preventing it from happening?

PS: I have to say, this extra movement of the circle could be any value, whether big or small.

Also: Slider value of +1.0 means the circle is inside the boundary on the right side, any extra drag of circle beyond that point would still keep the slider at +1.0, but now this +1.0 would correspond to the circle being outside the boundary. While slider value of -1.0 means the boundary on the left side.

Code for iOS 15.0, macOS 12.0

import SwiftUI

struct ContentView: View {
    
    private let maxCanvasVisibilitySize: CGSize = CGSize(width: 400.0, height: 300.0)
    private let circleSize: CGSize = CGSize(width: 50.0, height: 50.0)
    
    @State private var maxCanvasImaginarySize: CGSize = CGSize(width: 400.0, height: 300.0)

    @State private var circleLastOffset: CGSize = CGSize()
    @State private var circleCurrentOffset: CGSize = CGSize()
    @State private var circleIsDragging: Bool = Bool()
    
    @State private var percentage: CGFloat = CGFloat()
    
    var body: some View {
        
        
        VStack {
            
            GeometryReader { geometryValue in
                
                ZStack {
                    
                    Color.black
                    
                    Circle()
                        .fill(Color.red)
                        .frame(width: circleSize.width, height: circleSize.height)
                        .offset(circleCurrentOffset)
                        .gesture(circleDragGesture)
                    
                }
                .overlay(Rectangle().stroke(Color.blue, lineWidth: 4.0))
                
            }
            .frame(width: maxCanvasVisibilitySize.width, height: maxCanvasVisibilitySize.height)
            
            SliderView(percentage: $percentage)
                .frame(width: maxCanvasVisibilitySize.width)
            
        }
        .padding()
        .onChange(of: percentage) { newValue in
            if (!circleIsDragging) {
                circleCurrentOffset.width = newValue * ((maxCanvasVisibilitySize.width - circleSize.width)/2.0)
                circleLastOffset = circleCurrentOffset
                
            }
        }
        
        
        
    }
    
    private var circleDragGesture: some Gesture {
        
        return DragGesture(minimumDistance: CGFloat.zero, coordinateSpace: .local)
            .onChanged() { gestureValue in
                
                circleIsDragging = true
                
                circleCurrentOffset = CGSize(width: gestureValue.translation.width + circleLastOffset.width,
                                             height: gestureValue.translation.height + circleLastOffset.height)
                
                percentage = circleCurrentOffset.width/((maxCanvasVisibilitySize.width - circleSize.width)/2.0)
                
                
                
            }
            .onEnded() { gestureValue in
                circleLastOffset = circleCurrentOffset
                circleIsDragging = false
            }
        
    }
}


struct SliderView: View {
    
    init(percentage: Binding<CGFloat>) {
        self._percentage = percentage
    }
    
    @Binding var percentage: CGFloat
    
    @State private var knobLastOffset: CGFloat = CGFloat()
    @State private var knobCurrentOffset: CGFloat = CGFloat()
    @State private var knobIsDragging: Bool = Bool()
    
    private let knobSize: CGSize = CGSize(width: 50.0, height: 30.0)
    
    @State private var sliderGeometrySize: CGSize = CGSize()
    
    var body: some View {
        
        GeometryReader { geometryValue in
            
            ZStack {
                
                Color.white
                
                Color.blue.opacity(0.75)
                    .frame(width: knobSize.width)
                    .offset(x: knobCurrentOffset)
                    .gesture(knobDragGesture)
                
            }
            .onAppear { sliderGeometrySize = geometryValue.size }
            .onChange(of: geometryValue.size) { newValue in
                sliderGeometrySize = newValue
                
                let upperRangeOffset = (newValue.width - knobSize.width)/2.0
                let lowerRangeOffset = -upperRangeOffset
                
                knobCurrentOffset = offsetRegulatorWithPercentage(percentage: percentage, upperRangeOffset: upperRangeOffset, lowerRangeOffset: lowerRangeOffset)
                knobLastOffset = knobCurrentOffset
                
            }
            
        }
        .frame(height: knobSize.height)
        .onChange(of: percentage) { newValue in
            if (!knobIsDragging) {
                
                let upperRangeOffset = (sliderGeometrySize.width - knobSize.width)/2.0
                let lowerRangeOffset = -upperRangeOffset
                
                knobCurrentOffset = offsetRegulatorWithPercentage(percentage: newValue, upperRangeOffset: upperRangeOffset, lowerRangeOffset: lowerRangeOffset)
                knobLastOffset = knobCurrentOffset
                
            }
        }
        
    }
    
    private var knobDragGesture: some Gesture {
        
        return DragGesture(minimumDistance: CGFloat.zero, coordinateSpace: .local)
            .onChanged() { gestureValue in
                
                knobIsDragging = true
                let internalOffset = gestureValue.translation.width + knobLastOffset
                let upperRangeOffset = (sliderGeometrySize.width - knobSize.width)/2.0
                let lowerRangeOffset = -upperRangeOffset
                
                knobCurrentOffset = offsetRegulator(internalOffset: internalOffset, upperRangeOffset: upperRangeOffset, lowerRangeOffset: lowerRangeOffset)
                percentage = knobCurrentOffset / ((sliderGeometrySize.width - knobSize.width)/2.0)
                
            }
            .onEnded() { gestureValue in
                knobLastOffset = knobCurrentOffset
                knobIsDragging = false
            }
        
    }
    
    private func offsetRegulator(internalOffset: CGFloat, upperRangeOffset: CGFloat, lowerRangeOffset: CGFloat) -> CGFloat {
        
        if (internalOffset >= .zero) {
            
            if (internalOffset < upperRangeOffset)  {
                return internalOffset
            }
            else {
                return upperRangeOffset
            }
            
        }
        else {
            
            if (internalOffset > lowerRangeOffset)  {
                return internalOffset
            }
            else {
                return lowerRangeOffset
            }
            
        }
        
    }
    
    private func offsetRegulatorWithPercentage(percentage: CGFloat, upperRangeOffset: CGFloat, lowerRangeOffset: CGFloat) -> CGFloat {
        let internalOffset: CGFloat = percentage * upperRangeOffset
        return offsetRegulator(internalOffset: internalOffset, upperRangeOffset: upperRangeOffset, lowerRangeOffset: lowerRangeOffset)
        
    }
}

本文标签: swiftHow can I solve the outofrange percentage issue for slidersStack Overflow