The default behaviour of UISlider
is that its thumb isn't centered on start/end of track. Like below:
I would like to modify it's behaviour to get:
where thumb's center can be positioned on start or end.
I have tried to cover start/end with empty UIView
. Effect is that is look almost ok, but thumb has dropshadow that reveals my hack in some positions (which I can live with), however my hacky view covers a little the thumb and captures it's touch event (even when user interactions are disabled).
So I guess I would need to pack my hacky view between thumb and track. Possibly without manipulating its internal views. Any ideas?
UISlider class has a method thumbRect(forBounds:trackRect:value:) that you can override to control the drawing rectangle for the slider’s thumb image. So assuming that this space is 5 points (might vary depending on the thumb image you are using) we can do the following:
- Subclass
UISlider
and override thumbRect(forBounds:trackRect:value:)
to hook ourselves into the process
- Define the thumbSpace (I assumed it is 5), and from the thumbSpace we can calculate starting offset (5 points before the slider) and ending offset (5 points after the slider).
- Calculate the thumb translation in relation to current value, minValue and maxValue (In other words, based on the current value, we are shifting the thumb a little to cover the spaces we don't want to show)
- Return a shifted rectangle (after applying the x translation) to the super method to do the regular calculations
The complete code should be like the following:
class CustomSlider: UISlider {
let defaultThumbSpace: Float = 5
lazy var startingOffset: Float = 0 - defaultThumbSpace
lazy var endingOffset: Float = 2 * defaultThumbSpace
override func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect {
let xTranslation = startingOffset + (minimumValue + endingOffset) / maximumValue * value
return super.thumbRect(forBounds: bounds,
trackRect: rect.applying(CGAffineTransform(translationX: CGFloat(xTranslation),
y: 0)),
value: value)
}
}
Swift 5 version for lazy ones like me :)
class CustomSlider: UISlider {
private let trackHeight: CGFloat = 8
override func trackRect(forBounds bounds: CGRect) -> CGRect {
let point = CGPoint(x: bounds.minX, y: bounds.midY)
return CGRect(origin: point, size: CGSize(width: bounds.width, height: trackHeight))
}
private let thumbWidth: Float = 52
lazy var startingOffset: Float = 0 - (thumbWidth / 2)
lazy var endingOffset: Float = thumbWidth
override func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect {
let xTranslation = startingOffset + (minimumValue + endingOffset) / maximumValue * value
return super.thumbRect(forBounds: bounds, trackRect: rect.applying(CGAffineTransform(translationX: CGFloat(xTranslation), y: 0)), value: value)
}
}