UISlider with thumb center over the start and end

2019-06-24 09:10发布

问题:

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?

回答1:

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:

  1. Subclass UISlider and override thumbRect(forBounds:trackRect:value:) to hook ourselves into the process
  2. 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).
  3. 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)
  4. 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)
    }
}


回答2:

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)
}
}