Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS - UISlider jumps on first touch

Tags:

ios

uislider

I've added a UISlider to an iOS application I'm developing with Xcode 5.1 and I cannot get rid of the following annoying behavior. When I first touch the thumb, it jumps by a small amount.

Here's a quick example detailing the behavior.

When the slider thumb is initially positioned to the left of the center (e.g. at the minimum value), it jumps to the right. If I run the app in the iOS simulator, the thumb jumps upon first touch without any dragging. The thumb always jumps towards the center of the track. The only time it does not jump is when it is at the precise center of the track. This behavior appears even when the slider is not connected to anything.

Is there any way I can disable it?

like image 818
user3501984 Avatar asked Apr 05 '14 19:04

user3501984


2 Answers

Just came across this issue today. For some reason, you have to set a custom thumb image, and it works.

[self.yourSlider setThumbImage:[UIImage imageNamed:@"customThumb"] forState:UIControlStateNormal];
like image 148
scott Avatar answered Oct 02 '22 05:10

scott


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

change slicing attributes

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)
    }
}
like image 1
HotJard Avatar answered Oct 02 '22 05:10

HotJard