Meanwhile in 2019 and iOS 12 has that bug
An old question and still the relevant issue. So while accepted answer works correctly, it has one small and important issue - the thumb touch zone is getting smaller, while increasing touchable area in most scenario leads to new jumping (have tried overriding thumbRect(forBounds and beginTracking methods)
Let's fix it!
So the solution is not so complex but inconvenient a bit (need some image resources), still easier than re-implementing own Slider
The purpose of this class is to eliminte UIKit bug which is
jumping thumb during tapping.
The main hack is to replace original thumb with some image
Other code is extendinig tapping area which is:
1) setup an image with some side alpha offset, i.e. a circle whithin larger rectangle
setThumbImage(thumb, for: .normal)
2) larger image means larger thumb and as the result track on min and max value
is visible under image's alpha area. So the next step is to set min and max track
image which is track on the left and rigth sides respectevly,
setMinimumTrackImage(anImage, for: .normal)
setMaximumTrackImage(maxSliderImage, for: .normal)
but the image also has a some alpha zone which is needed to 'hide' track on
the sides of thumb's (with min/max values where it's purely visible).
4) while we have larger thumb and track visually decreased (respectively to slider bounds width)
need to increase visible part of the track by overriding trackRect(forBounds method.
we won't have out of slider bounds track as part of min/max images are transparent
5) as one of goals is to increase thumb's touch zone need to override
point(inside point:, with event:) method to make touchable zone on the sides
PS: don't forget to set in xcassets min and max slider tracks attributes:
1) Slicing horizontal and left/right insets
2) Streching techninque
class Slider: UISlider {
/// The value is distance between an image edge and thumb,
/// Assume that's an image scheme: [...o...] so 3 dots here is imageSideOffset, where 'o' is a thumb
/// as we want to hide that zone
private let imageSideOffset: CGFloat = 30
override func awakeFromNib() {
super.awakeFromNib()
if let thumb = UIImage(named: "slider_thumb"),
let minSliderImage = UIImage(named: "min_slider_track"),
let maxSliderImage = UIImage(named: "max_slider_track") {
setThumbImage(thumb, for: .normal)
setMinimumTrackImage(minSliderImage, for: .normal)
setMaximumTrackImage(maxSliderImage, for: .normal)
clipsToBounds = true
} else {
assertionFailure("failed to load slider's images")
}
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return bounds.insetBy(dx: -imageSideOffset, dy: 0).contains(point)
}
override func trackRect(forBounds bounds: CGRect) -> CGRect {
let superRect = super.trackRect(forBounds: bounds)
guard let _ = self.thumbImage(for: .normal) else {
return superRect
}
return superRect.insetBy(dx: -imageSideOffset, dy: 0)
}
}