Skip to content

Instantly share code, notes, and snippets.

@alfian0
Last active August 11, 2020 08:42
Show Gist options
  • Save alfian0/797d06c5122bcae7b7e80e2a66649ceb to your computer and use it in GitHub Desktop.
Save alfian0/797d06c5122bcae7b7e80e2a66649ceb to your computer and use it in GitHub Desktop.
import UIKit
@IBDesignable
class DoubleSlider: UIControl {
@IBInspectable
var minimumValue: CGFloat = 0 {
didSet {
updateLayerFrames()
}
}
@IBInspectable
var maximumValue: CGFloat = 1 {
didSet {
updateLayerFrames()
}
}
@IBInspectable
var lowerValue: CGFloat = 0.2 {
didSet {
updateLayerFrames()
}
}
@IBInspectable
var upperValue: CGFloat = 0.8 {
didSet {
updateLayerFrames()
}
}
@IBInspectable
var thumbImage: UIImage? = nil {
didSet {
lowerThumbImageView.image = thumbImage
upperThumbImageView.image = thumbImage
}
}
override var frame: CGRect {
didSet {
updateLayerFrames()
}
}
private let trackLayer: CALayer = {
let layer = CALayer()
layer.backgroundColor = UIView().tintColor.cgColor
return layer
}()
private let lowerThumbImageView: UIImageView = {
let image = UIImageView()
image.tintColor = .orange
return image
}()
private let upperThumbImageView: UIImageView = {
let image = UIImageView()
image.tintColor = .orange
return image
}()
private let size: CGFloat = 24
private let trackHeight: CGFloat = 2
private var previousLocation = CGPoint()
private var feedbackGenerator: UIImpactFeedbackGenerator? = {
let haptic = UIImpactFeedbackGenerator.init(style: .light)
haptic.prepare()
return haptic
}()
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
layer.addSublayer(trackLayer)
addSubview(lowerThumbImageView)
addSubview(upperThumbImageView)
updateLayerFrames()
}
private func updateLayerFrames() {
trackLayer.frame = CGRect(x: 0, y: (bounds.height/2)-(trackHeight/2), width: bounds.width, height: trackHeight)
trackLayer.setNeedsDisplay()
lowerThumbImageView.frame = CGRect(origin: thumbOriginForValue(lowerValue),
size: CGSize(width: size, height: size))
upperThumbImageView.frame = CGRect(origin: thumbOriginForValue(upperValue),
size: CGSize(width: size, height: size))
}
func positionForValue(_ value: CGFloat) -> CGFloat {
let percent = value < minimumValue ? value : value / maximumValue
return bounds.width * percent
}
private func thumbOriginForValue(_ value: CGFloat) -> CGPoint {
let x = positionForValue(value) - (size/2)
return CGPoint(x: x, y: (bounds.height/2)-size)
}
}
extension DoubleSlider {
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
previousLocation = touch.location(in: self)
if lowerThumbImageView.frame.contains(previousLocation) {
lowerThumbImageView.isHighlighted = true
} else if upperThumbImageView.frame.contains(previousLocation) {
upperThumbImageView.isHighlighted = true
}
self.feedbackGenerator?.impactOccurred()
return lowerThumbImageView.isHighlighted || upperThumbImageView.isHighlighted
}
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let location = touch.location(in: self)
let deltaLocation = location.x - previousLocation.x
let deltaValue = (maximumValue - minimumValue) * deltaLocation / bounds.width
previousLocation = location
if lowerThumbImageView.isHighlighted {
lowerValue += deltaValue
lowerValue = boundValue(lowerValue, toLowerValue: minimumValue,
upperValue: upperValue)
} else if upperThumbImageView.isHighlighted {
upperValue += deltaValue
upperValue = boundValue(upperValue, toLowerValue: lowerValue,
upperValue: maximumValue)
}
CATransaction.begin()
CATransaction.setDisableActions(true)
updateLayerFrames()
CATransaction.commit()
sendActions(for: .valueChanged)
return true
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
lowerThumbImageView.isHighlighted = false
upperThumbImageView.isHighlighted = false
}
override func cancelTracking(with event: UIEvent?) {
lowerThumbImageView.isHighlighted = false
upperThumbImageView.isHighlighted = false
}
private func boundValue(_ value: CGFloat, toLowerValue lowerValue: CGFloat,
upperValue: CGFloat) -> CGFloat {
return min(max(value, lowerValue), upperValue)
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.isKind(of: UIPanGestureRecognizer.self) {
return false
} else {
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
}
}
@alfian0
Copy link
Author

alfian0 commented Aug 3, 2020

Screen Shot 2020-08-03 at 16 54 48

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment