How To Implement ShouldChangeValue(to: Int) For UISlider?
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?
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:
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.
Related Questions
- → Function Undefined in Axios promise
- → What are the pluses/minuses of different ways to configure GPIOs on the Beaglebone Black?
- → Click to navigate on mobile devices
- → Playing Video - Server is not correctly configured - 12939
- → How to allow api access to android or ios app only(laravel)?
- → Axios array map callback
- → Access the Camera and CameraRoll on Android using React Native?
- → Update React [Native] View on Day Change
- → Shopify iOS SDK - issue converting BuyProductVariant to BuyProduct
- → BigCommerce and shopify API
- → Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `ListView`
- → React Native - Differences between Android and IOS
- → What is the difference between React Native and React?