How can we get the range between two thumbs in UISlider
. My application contains price range selection i didn't get with a single thumb. Here i need to show the range on labels above the thumbs also these labels are inside the images.

- 701
- 5
- 14

- 146
- 1
- 8
-
1Hope this helps: http://stackoverflow.com/a/30589205/775896 – Mrunal Mar 23 '17 at 09:21
3 Answers
We cannot give two thumbs for one slider because we cannot change UI But we can give With our own design. For that first we have to take two buttons and two UIViews We need to change the buttons positions according to user touch and also change UI widths based on buttons positions. We have have a third party frame works like NMRangeSlider
and VPRangeSlider
.
I've created a filter without pods just for curious with next API:
let slider = DoubledSlider()
slider.minimumValue = 0
slider.maximumValue = 100
To fetch UIEvent.valueChanged
simply do this:
slider.addTarget(self, action: #selector(self.didChangeSliderValue), for: .valueChanged)
And finally handle values like below:
@objc private func didChangeSliderValue() {
print(self.slider.values.minimum)
print(self.slider.values.maximum)
}
And this looks like
You can customize API, UI or whatever. All code source is open. Enjoy.

- 737
- 1
- 8
- 19
-
Hi @shokuroff , I am trying to use this but can you tell me what is `FeedbackManager.shared.vibrate()` is for ? – Saad Ullah Oct 22 '20 at 05:26
-
@SaadUllah hi, check [here](https://gist.github.com/shokuroff/933fd8001cc590db6bf9617cd4c0f51b)! This class is responsible on calling vibration on device when it needs. You can implement you own implementation or hust use this class in the reference (gist). – devshok Oct 23 '20 at 01:39
-
Based on shokuroff's answer, I have updated this double slider into the following version. It gives the following improvements (at least for me):
- Based on SnapKit
- I can use bigger Thumb image. Especially ones with shadows. The original code some how rely on thumb image size. Thumb Image I'm using
- Thumbs can be a little outside the progress bar's frame. Like UISlider does.
UISlider and this DoubleSlider
Here's the code:
import SnapKit
import UIKit
public final class DoubledSlider: UIControl {
public struct Constant {
let height: CGFloat = 4.0
let trackForegroundImage: UIImage = // Use an image for your progress bar
let thumbWrapperSize: CGFloat = 31.0
let thumbSize: CGFloat = 56.0
let miniThumbDistance: CGFloat = 5.0
let edgeMakeup: CGFloat = 5.0
}
private let constants = Constant()
var trackBackgroundColor: UIColor {
if traitCollection.userInterfaceStyle == .dark {
// swiftlint:disable force_unwrapping
return UIColor(hexString: "333333")!
} else {
// swiftlint:disable force_unwrapping
return UIColor(hexString: "#DDDDDD")!
}
}
private lazy var track = UIView()
private lazy var activeTrack = UIImageView()
private lazy var leftThumbWrapper = UIImageView()
private lazy var rightThumbWrapper = UIImageView()
private lazy var leftThumb = UIImageView(image: /* Your Image with shadows. */)
private lazy var rightThumb = UIImageView(image: /* Your Image with shadows. */)
// MARK: - Initialization
public convenience init(minimumValue: Float = .zero, maximumValue: Float = .zero) {
self.init()
self.minimumValue = minimumValue
self.maximumValue = maximumValue
}
private override init(frame: CGRect = .zero) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Setup UI
private func setupUI() {
backgroundColor = .clear
clipsToBounds = false
setupTrack()
setupThumb()
setupConstraints()
}
private func setupTrack() {
addSubview(track)
activeTrack = UIImageView(image: constants.trackForegroundImage)
track.addSubview(activeTrack)
track.layer.masksToBounds = true
track.layer.cornerRadius = constants.height / 2
track.backgroundColor = trackBackgroundColor
track.clipsToBounds = true
}
private func setupThumb() {
addSubview(leftThumbWrapper)
addSubview(rightThumbWrapper)
leftThumbWrapper.addSubview(leftThumb)
rightThumbWrapper.addSubview(rightThumb)
[leftThumbWrapper, rightThumbWrapper].forEach {
$0.clipsToBounds = false
$0.contentMode = .center
$0.isUserInteractionEnabled = true
let gesture = UIPanGestureRecognizer(target: self, action: #selector(self.dragThumb(using:)))
$0.addGestureRecognizer(gesture)
}
[leftThumb, rightThumb].forEach {
$0.clipsToBounds = false
$0.contentMode = .center
}
self.leftThumbWrapper.tag = 1
self.rightThumbWrapper.tag = 2
}
private func setupConstraints() {
track.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview()
make.centerY.equalToSuperview()
make.height.equalTo(constants.height)
}
leftThumbWrapper.snp.makeConstraints { make in
make.size.equalTo(constants.thumbWrapperSize)
make.top.bottom.equalToSuperview()
leftThumbConstraint = make.leading.equalToSuperview().offset(-constants.edgeMakeup).constraint
make.right.lessThanOrEqualTo(rightThumbWrapper)
}
rightThumbWrapper.snp.makeConstraints { make in
make.size.equalTo(constants.thumbWrapperSize)
make.top.bottom.equalToSuperview()
rightThumbConstraint = make.trailing.equalToSuperview()
.offset(constants.edgeMakeup)
.constraint
make.left.greaterThanOrEqualTo(leftThumbWrapper)
}
leftThumb.snp.makeConstraints { make in
make.size.equalTo(constants.thumbSize)
make.center.equalToSuperview()
}
rightThumb.snp.makeConstraints { make in
make.size.equalTo(constants.thumbSize)
make.center.equalToSuperview()
}
activeTrack.snp.makeConstraints { make in
leftActiveTrackConstraint = make.leading.equalToSuperview()
.offset(-constants.edgeMakeup)
.constraint
rightActiveTrackConstraint = make.trailing.equalToSuperview()
.offset(constants.edgeMakeup)
.constraint
make.centerY.equalToSuperview()
make.height.equalTo(constants.height)
}
}
// MARK: - Drag
@objc private func dragThumb(using gesture: UIPanGestureRecognizer) {
guard let view = gesture.view else { return }
let translation = gesture.translation(in: self)
switch gesture.state {
case .began:
if [1, 2].contains(view.tag) { bringSubviewToFront(view) }
case .changed:
self.handleDragging(view, withTranslation: translation)
case .ended:
self.handleEndedDragging(view)
default:
break
}
}
private func handleDragging(_ view: UIView, withTranslation translation: CGPoint) {
switch view.tag {
case 1:
handleDraggingLeftThumb(view, withTranslation: translation)
case 2:
handleDraggingRightThumb(view, withTranslation: translation)
default:
break
}
}
private func handleDraggingLeftThumb(_ thumb: UIView, withTranslation translation: CGPoint) {
let newConstant = translation.x + self.leftThumbConstraintLastConstant
guard newConstant >= -constants.edgeMakeup else {
leftThumbWrapper.snp.updateConstraints { make in
make.leading.equalToSuperview().offset(-constants.edgeMakeup)
}
activeTrack.snp.updateConstraints { make in
make.leading.equalToSuperview().offset(-constants.edgeMakeup)
}
self.updateValues {
self.sendActions(for: .valueChanged)
}
return
}
if thumbesInOnePlace(byDraggingThumb: thumb) && translation.x > .zero { return }
leftThumbWrapper.snp.updateConstraints { make in
make.leading.equalToSuperview().offset(newConstant)
}
activeTrack.snp.updateConstraints { make in
make.leading.equalToSuperview().offset(newConstant + constants.edgeMakeup)
}
self.updateValues {
self.sendActions(for: .valueChanged)
}
}
private func handleDraggingRightThumb(_ thumb: UIView, withTranslation translation: CGPoint) {
let newConstant = translation.x + rightThumbConstraintLastConstant
guard newConstant <= constants.edgeMakeup else {
rightThumbWrapper.snp.updateConstraints { make in
make.trailing.equalToSuperview().offset(constants.edgeMakeup)
}
activeTrack.snp.updateConstraints { make in
make.trailing.equalToSuperview().offset(constants.edgeMakeup)
}
self.updateValues {
self.sendActions(for: .valueChanged)
}
return
}
if thumbesInOnePlace(byDraggingThumb: thumb) && translation.x < .zero { return }
rightThumbWrapper.snp.updateConstraints { make in
make.trailing.equalToSuperview().offset(newConstant)
}
activeTrack.snp.updateConstraints { make in
make.trailing.equalToSuperview().offset(newConstant - constants.edgeMakeup)
}
self.updateValues {
self.sendActions(for: .valueChanged)
}
}
private func handleEndedDragging(_ view: UIView) {
switch view.tag {
case 1:
leftThumbConstraintLastConstant = leftThumbConstant
leftActiveTrackConstraintLastConstant = leftActiveTrackConstant
case 2:
rightThumbConstraintLastConstant = rightThumbConstant
rightActiveTrackConstraintLastConstant = rightActiveTrackConstant
default:
break
}
}
private func thumbesInOnePlace(byDraggingThumb thumb: UIView) -> Bool {
let difference = abs(self.leftThumbWrapper.center.x - self.rightThumbWrapper.center.x)
let onePlace = difference < constants.miniThumbDistance
if onePlace {
forceThumbsPositions(byActiveThumb: thumb)
}
return onePlace
}
private func forceThumbsPositions(byActiveThumb thumb: UIView) {
switch thumb.tag {
case 1:
let newConstant = rightThumbWrapper.frame.origin.x
leftThumbWrapper.snp.updateConstraints { make in
make.leading.equalToSuperview().offset(newConstant)
}
leftThumbConstraintLastConstant = newConstant
activeTrack.snp.updateConstraints { make in
make.leading.equalToSuperview().offset(newConstant)
}
leftActiveTrackConstraintLastConstant = newConstant
case 2:
let centerXDifference = rightThumbWrapper.center.x - leftThumbWrapper.center.x
let newConstant = rightThumbConstant - centerXDifference
rightThumbWrapper.snp.updateConstraints { make in
make.trailing.equalToSuperview().offset(newConstant)
}
rightThumbConstraintLastConstant = newConstant
activeTrack.snp.updateConstraints { make in
make.trailing.equalToSuperview().offset(newConstant)
}
rightActiveTrackConstraintLastConstant = newConstant
default:
break
}
}
// MARK: - Constraints as value
private var leftThumbConstraint: Constraint?
private var rightThumbConstraint: Constraint?
private var leftThumbConstant: CGFloat {
return leftThumbConstraint?.layoutConstraints[0].constant ?? 0
}
private var rightThumbConstant: CGFloat {
return rightThumbConstraint?.layoutConstraints[0].constant ?? 0
}
private var leftThumbConstraintLastConstant: CGFloat = .zero
private var rightThumbConstraintLastConstant: CGFloat = .zero
private var leftActiveTrackConstraint: Constraint?
private var rightActiveTrackConstraint: Constraint?
private var leftActiveTrackConstant: CGFloat {
return leftActiveTrackConstraint?.layoutConstraints[0].constant ?? 0
}
private var rightActiveTrackConstant: CGFloat {
return rightActiveTrackConstraint?.layoutConstraints[0].constant ?? 0
}
private var leftActiveTrackConstraintLastConstant: CGFloat = .zero
private var rightActiveTrackConstraintLastConstant: CGFloat = .zero
// MARK: - Public API
public var minimumValue: Float = .zero
public var maximumValue: Float = .zero
public var values: (minimum: Float, maximum: Float) {
get { return (minimumValueNow, maximumValueNow) }
set {
var newMin: Float = .zero
var newMax: Float = .zero
if newValue.0 <= .zero {
newMin = minimumValue
}
if newValue.0 <= minimumValue {
newMin = minimumValue
}
if newValue.0 > newValue.1 {
newMin = newValue.1
}
if newValue.1 <= .zero {
newMax = minimumValue
}
if newValue.1 >= maximumValue {
newMax = maximumValue
}
if newValue.1 < newValue.0 {
newMax = newValue.0
}
self.minimumValueNow = newMin
self.maximumValueNow = newMax
}
}
// MARK: - Private API
private var minimumValueNow: Float = .zero
private var maximumValueNow: Float = .zero
private func updateValues(_ completion: @escaping () -> Void) {
minimumValueNow = newMinimalValue
maximumValueNow = newMaximumValue
let originXDifference = abs(rightThumbWrapper.frame.origin.x - leftThumbWrapper.frame.origin.x)
if originXDifference < constants.miniThumbDistance {
if minimumValueNow == minimumValue {
maximumValueNow = minimumValueNow
} else {
minimumValueNow = maximumValueNow
}
}
completion()
}
private var newMinimalValue: Float {
let leftThumbOriginX = leftThumbConstant
let distancePassed = leftThumbOriginX + constants.edgeMakeup
let totalDistance = track.frame.size.width +
2 * constants.edgeMakeup -
constants.thumbWrapperSize
let distancePassedFraction = distancePassed / totalDistance
if leftThumbOriginX <= -constants.edgeMakeup {
return minimumValue
}
if distancePassed > totalDistance {
return maximumValue
}
let newValue: Float = {
let difference = maximumValue - minimumValue
let addingValue = Float(distancePassedFraction) * difference
return minimumValue + addingValue
}()
return newValue
}
private var newMaximumValue: Float {
let constraintConstant = rightThumbConstant
let distancePassed = abs(constraintConstant - constants.edgeMakeup)
let totalDistance = track.frame.size.width +
2 * constants.edgeMakeup -
constants.thumbWrapperSize
let distancePassedFraction = distancePassed / totalDistance
if constraintConstant >= constants.edgeMakeup {
return maximumValue
}
if distancePassed > totalDistance {
return self.minimumValue
}
let newValue: Float = {
let difference = maximumValue - minimumValue
let addingValue = difference * Float(distancePassedFraction)
return maximumValue - addingValue
}()
return newValue
}
}

- 767
- 1
- 6
- 18