Ad

How To Implement ShouldChangeValue(to: Int) For UISlider?

- 1 answer

I simply have UISlider with minValue 0 and maxValue 100. My currentValue is 40.

  • I have 6 sliders with custom minValue and maxValue for each of them.
  • I also have some kind of validation (total sum should not be greater than 200).

Is there a way to check if currentValue from @IBAction

@objc private func didValueChanged(slider: UISlider) {

    // check if it is not over 200, 
    // if not then set previous value. 
    // How do I get previous value of slider?
}

is not over my limit and do nothing then otherwise set a previous value to slider?

Ad

Answer

It's not clear exactly what you want to do when the sum of the sliders exceeds the 200 limit, but the general idea...

You'll want to track each slider's last valid value. When the user stops dragging:

  • get the value of the "active" slider
  • get sum of the other 5 sliders
  • add the "active" value to the sum
  • if the sum is out-of-range (sum > 200)
    • reset to the active slider's saved value
  • else
    • update the active slider's saved value

So, give your sliders this action:

slider.addTarget(self, action: #selector(didStopSliding(slider:)), for: [.touchUpInside, .touchUpOutside, .touchCancel])

which will be called when the users stops sliding (lifts touch or slides off-screen or action is cancelled).

You can keep your current:

slider.addTarget(self, action: #selector(didValueChanged(slider:)), for: .valueChanged)

if you also want to do something while dragging (such as updating value label(s)).

Here's a quick example:

// struct to associate values and display labels with the UISlider
struct MySliderStruct {
    var minValue: Float = 0
    var maxValue: Float = 0
    var curValue: Float = 0
    var minLabel: UILabel = UILabel()
    var curLabel: UILabel = UILabel()
    var maxLabel: UILabel = UILabel()
    var theSlider: UISlider = UISlider()
}

class SliderCheckVC: UIViewController {
    
    var theSliders: [MySliderStruct] = []
    
    let runningTotalLabel: UILabel = {
        let v = UILabel()
        v.textAlignment = .center
        return v
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let minMax: [[Float]] = [
            [10, 40],
            [20, 50],
            [0, 10],
            [0, 50],
            [10, 60],
            [0, 100],
        ]
        let colors: [UIColor] = [
            .init(red: 1.0, green: 0.8, blue: 0.8, alpha: 1.0),
            .init(red: 0.5, green: 1.0, blue: 0.5, alpha: 1.0),
            .init(red: 0.8, green: 0.8, blue: 1.0, alpha: 1.0),
            .init(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0),
            .init(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0),
            .init(red: 0.0, green: 1.0, blue: 1.0, alpha: 1.0),
        ]
        
        let outerStack: UIStackView = {
            let v = UIStackView()
            v.axis = .vertical
            v.spacing = 16
            v.translatesAutoresizingMaskIntoConstraints = false
            return v
        }()
        
        for (pair, clr) in zip(minMax, colors) {

            // vertical stack view for labels + slider
            let vStack: UIStackView = {
                let v = UIStackView()
                v.axis = .vertical
                v.spacing = 4
                return v
            }()
            // horizontal stack view for the labels
            let hStack: UIStackView = {
                let v = UIStackView()
                v.distribution = .fillEqually
                return v
            }()
            let vMin: UILabel = {
                let v = UILabel()
                v.textAlignment = .left
                v.font = .systemFont(ofSize: 13.0)
                v.text = formatVal(pair[0])
                return v
            }()
            let vCur: UILabel = {
                let v = UILabel()
                v.textAlignment = .center
                v.font = .systemFont(ofSize: 13.0)
                v.text = formatVal(pair[0])
                return v
            }()
            let vMax: UILabel = {
                let v = UILabel()
                v.textAlignment = .right
                v.font = .systemFont(ofSize: 13.0)
                v.text = formatVal(pair[1])
                return v
            }()
            hStack.addArrangedSubview(vMin)
            hStack.addArrangedSubview(vCur)
            hStack.addArrangedSubview(vMax)
            
            let slider = UISlider()
            slider.minimumValue = pair[0]
            slider.maximumValue = pair[1]
            slider.setValue(pair[0], animated: false)
            slider.addTarget(self, action: #selector(didValueChanged(slider:)), for: .valueChanged)
            slider.addTarget(self, action: #selector(didStopSliding(slider:)), for: [.touchUpInside, .touchUpOutside, .touchCancel])

            vStack.backgroundColor = clr

            vStack.addArrangedSubview(hStack)
            vStack.addArrangedSubview(slider)
            outerStack.addArrangedSubview(vStack)

            let thisSlider: MySliderStruct = MySliderStruct(minValue: pair[0],
                                                            maxValue: pair[1],
                                                            curValue: pair[0],
                                                            minLabel: vMin,
                                                            curLabel: vCur,
                                                            maxLabel: vMax,
                                                            theSlider: slider
            )
            theSliders.append(thisSlider)
        }

        // running total line
        let hStack: UIStackView = {
            let v = UIStackView()
            v.distribution = .fillEqually
            return v
        }()
        let v1 = UILabel()
        v1.text = "Current Total"
        v1.textAlignment = .center
        runningTotalLabel.textAlignment = .center
        hStack.addArrangedSubview(v1)
        hStack.addArrangedSubview(runningTotalLabel)
        outerStack.addArrangedSubview(hStack)
        
        view.addSubview(outerStack)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            outerStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 12.0),
            outerStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            outerStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
        ])

        updateTotalLabel()
    }

    @objc private func didStopSliding(slider: UISlider) {

        guard let thisSliderIDX = theSliders.firstIndex(where: { $0.theSlider == slider})
        else {
            print("Invalide slider setup")
            return
        }

        // get the value of "active" slider
        let thisValue: Float = slider.value.rounded()
        
        // get sum of all other sliders, plus
        //  this slider value
        var total: Float = 0
        for i in 0..<theSliders.count {
            if i == thisSliderIDX {
                total += thisValue
            } else {
                total += theSliders[i].curValue
            }
        }

        // if sum is less-than max allowed
        if total < 200.0 {
            // update array with new value
            theSliders[thisSliderIDX].curValue = thisValue
            // move slider to rounded Int value
            slider.setValue(thisValue, animated: true)
        } else {
            // reset to last value
            slider.setValue(theSliders[thisSliderIDX].curValue, animated: true)
            // update this slider's current value label
            theSliders[thisSliderIDX].curLabel.text = formatVal(theSliders[thisSliderIDX].curValue)
        }

        updateTotalLabel()
        
    }
    @objc private func didValueChanged(slider: UISlider) {
        // do something as the slider is dragged
        guard let thisSliderIDX = theSliders.firstIndex(where: { $0.theSlider == slider})
        else {
            print("Invalide slider setup")
            return
        }

        // get the value of "active" slider
        let thisValue: Float = slider.value.rounded()

        // update this slider's current value label
        theSliders[thisSliderIDX].curLabel.text = formatVal(thisValue)

        // get sum of all other sliders, plus
        //  this slider value
        var total: Float = 0
        for i in 0..<theSliders.count {
            if i == thisSliderIDX {
                total += thisValue
            } else {
                total += theSliders[i].curValue
            }
        }
        
        // update the running total label here, because
        //  we might throw out this value
        runningTotalLabel.text = formatVal(total)
    }
    
    func updateTotalLabel() {
        let sumOfValues = theSliders.reduce(0) { $0 + ($1.curValue ) }
        runningTotalLabel.text = formatVal(sumOfValues)
    }
    func formatVal(_ val: Float) -> String {
        return "\(Int(val))"
    }
}

Looks like this:

enter image description here

On "done sliding" if that slider is dragged to a value that will cause the sum to exceed 200, that slider will be reset to it's last "valid" value.

Ad
source: stackoverflow.com
Ad