您需要处理各种问题。
首先,看看这三个“默认”UISlider 控件。拇指垂直偏移所以我们可以看到轨道,红色虚线轮廓是滑块框架:
- 顶部是最小值(尽可能向左)
- 中间是50%
- 而底部是最大值(尽可能远)
如我们所见,拇指的水平中心在轨迹矩形的末端(边界)不是。
如果我们希望刻度线与拇指的水平中心对齐,我们需要根据拇指中心的原点和宽度在最小值和最大值处计算 x 位置:
下一个问题是您可能不希望轨道 rect / images 延伸到刻度线的左/右。
这样的话,我们需要清除内置的轨迹图像并绘制我们自己的:
以下是一些您可以尝试使用的示例代码:
protocol TickerSliderDelegate: NSObject {
func sliderChanged(_ newValue: Int, sender: Any)
}
extension TickerSliderDelegate {
// make this delegate func optional
func sliderChanged(_ newValue: Int, sender: Any) {}
}
class TickerSlider: UISlider {
var delegate: TickerSliderDelegate?
var stepCount = 12
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
// clear min and max track images
// because we'll be drawing our own
setMinimumTrackImage(UIImage(), for: [])
setMaximumTrackImage(UIImage(), for: [])
}
override func draw(_ rect: CGRect) {
super.draw(rect)
// get the track rect
let trackR: CGRect = self.trackRect(forBounds: bounds)
// get the thumb rect at min and max values
let minThumbR: CGRect = self.thumbRect(forBounds: bounds, trackRect: trackR, value: minimumValue)
let maxThumbR: CGRect = self.thumbRect(forBounds: bounds, trackRect: trackR, value: maximumValue)
// usable width is center of thumb to center of thumb at min and max values
let usableWidth: CGFloat = maxThumbR.midX - minThumbR.midX
// Tick Height (or use desired explicit height)
let tickHeight: CGFloat = bounds.height
// "gap" between tick marks
let stepWidth: CGFloat = usableWidth / CGFloat(stepCount)
// a reusable path
var pth: UIBezierPath!
// a reusable point
var pt: CGPoint!
// new path
pth = UIBezierPath()
// left end of our track rect
pt = CGPoint(x: minThumbR.midX, y: bounds.height * 0.5)
// top of vertical tick lines
pt.y = (bounds.height - tickHeight) * 0.5
// we have to draw stepCount + 1 lines
// so use
// 0...stepCount
// not
// 0..<stepCount
for _ in 0...stepCount {
pth.move(to: pt)
pth.addLine(to: CGPoint(x: pt.x, y: pt.y + tickHeight))
pt.x += stepWidth
}
UIColor.lightGray.setStroke()
pth.stroke()
// new path
pth = UIBezierPath()
// left end of our track lines
pt = CGPoint(x: minThumbR.midX, y: bounds.height * 0.5)
// move to left end
pth.move(to: pt)
// draw the "right-side" of the track first
// it will be the full width of "our track"
pth.addLine(to: CGPoint(x: pt.x + usableWidth, y: pt.y))
pth.lineWidth = 3
UIColor.lightGray.setStroke()
pth.stroke()
// new path
pth = UIBezierPath()
// move to left end
pth.move(to: pt)
// draw the "left-side" of the track on top of the "right-side"
// at percentage width
let rng: Float = maximumValue - minimumValue
let val: Float = value - minimumValue
let pct: Float = val / rng
pth.addLine(to: CGPoint(x: pt.x + (usableWidth * CGFloat(pct)), y: pt.y))
pth.lineWidth = 3
UIColor.systemBlue.setStroke()
pth.stroke()
}
override func setValue(_ value: Float, animated: Bool) {
// don't allow value outside range of min and max values
let newVal: Float = min(max(minimumValue, value), maximumValue)
super.setValue(newVal, animated: animated)
// we need to trigger draw() when the value changes
setNeedsDisplay()
let steps: Float = Float(stepCount)
let rng: Float = maximumValue - minimumValue
// get the percentage along the track
let pct: Float = newVal / rng
// use that pct to get the rounded step position
let pos: Float = round(steps * pct)
// tell the delegate which Tick the thumb snapped to
delegate?.sliderChanged(Int(pos), sender: self)
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
super.endTracking(touch, with: event)
let steps: Float = Float(stepCount)
let rng: Float = maximumValue - minimumValue
// get the percentage along the track
let pct: Float = value / rng
// use that pct to get the rounded step position
let pos: Float = round(steps * pct)
// use that pos to calculate the new percentage
let newPct: Float = (pos / steps)
let newVal: Float = minimumValue + (rng * newPct)
self.value = newVal
}
override var bounds: CGRect {
willSet {
// we need to trigger draw() when the bounds changes
setNeedsDisplay()
}
}
}
注意:这只是此任务的一种方法(并且是仅示例代码)。搜索会出现许多您可能想要查看的其他方法/示例/等。
编辑
这是一个非常轻微的修改版本,以获得更“点准确”的刻度线/拇指对齐:
class TickerSlider: UISlider {
var delegate: TickerSliderDelegate?
var stepCount = 12
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
// clear min and max track images
// because we'll be drawing our own
setMinimumTrackImage(UIImage(), for: [])
setMaximumTrackImage(UIImage(), for: [])
// if we're using a custom thumb image
if let img = UIImage(named: "CustomThumbA") {
self.setThumbImage(img, for: [])
}
}
override func draw(_ rect: CGRect) {
super.draw(rect)
// get the track rect
let trackR: CGRect = self.trackRect(forBounds: bounds)
// get the thumb rect at min and max values
let minThumbR: CGRect = self.thumbRect(forBounds: bounds, trackRect: trackR, value: minimumValue)
let maxThumbR: CGRect = self.thumbRect(forBounds: bounds, trackRect: trackR, value: maximumValue)
// usable width is center of thumb to center of thumb at min and max values
let usableWidth: CGFloat = maxThumbR.midX - minThumbR.midX
// Tick Height (or use desired explicit height)
let tickHeight: CGFloat = bounds.height
// a reusable path
var pth: UIBezierPath!
// a reusable point
var pt: CGPoint!
// new path
pth = UIBezierPath()
pt = .zero
// top of vertical tick lines
pt.y = (bounds.height - tickHeight) * 0.5
// we have to draw stepCount + 1 lines
// so use
// 0...stepCount
// not
// 0..<stepCount
for i in 0...stepCount {
// get center of Thumb at each "step"
let aThumbR: CGRect = self.thumbRect(forBounds: bounds, trackRect: trackR, value: Float(i) / Float(stepCount))
pt.x = aThumbR.midX
pth.move(to: pt)
pth.addLine(to: CGPoint(x: pt.x, y: pt.y + tickHeight))
}
UIColor.lightGray.setStroke()
pth.stroke()
// new path
pth = UIBezierPath()
// left end of our track lines
pt = CGPoint(x: minThumbR.midX, y: bounds.height * 0.5)
// move to left end
pth.move(to: pt)
// draw the "right-side" of the track first
// it will be the full width of "our track"
pth.addLine(to: CGPoint(x: pt.x + usableWidth, y: pt.y))
pth.lineWidth = 3
UIColor.lightGray.setStroke()
pth.stroke()
// new path
pth = UIBezierPath()
// move to left end
pth.move(to: pt)
// draw the "left-side" of the track on top of the "right-side"
// at percentage width
let rng: Float = maximumValue - minimumValue
let val: Float = value - minimumValue
let pct: Float = val / rng
pth.addLine(to: CGPoint(x: pt.x + (usableWidth * CGFloat(pct)), y: pt.y))
pth.lineWidth = 3
UIColor.systemBlue.setStroke()
pth.stroke()
}
override func setValue(_ value: Float, animated: Bool) {
// don't allow value outside range of min and max values
let newVal: Float = min(max(minimumValue, value), maximumValue)
super.setValue(newVal, animated: animated)
// we need to trigger draw() when the value changes
setNeedsDisplay()
let steps: Float = Float(stepCount)
let rng: Float = maximumValue - minimumValue
// get the percentage along the track
let pct: Float = newVal / rng
// use that pct to get the rounded step position
let pos: Float = round(steps * pct)
// tell the delegate which Tick the thumb snapped to
delegate?.sliderChanged(Int(pos), sender: self)
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
super.endTracking(touch, with: event)
let steps: Float = Float(stepCount)
let rng: Float = maximumValue - minimumValue
// get the percentage along the track
let pct: Float = value / rng
// use that pct to get the rounded step position
let pos: Float = round(steps * pct)
// use that pos to calculate the new percentage
let newPct: Float = (pos / steps)
let newVal: Float = minimumValue + (rng * newPct)
self.value = newVal
}
override var bounds: CGRect {
willSet {
// we need to trigger draw() when the bounds changes
setNeedsDisplay()
}
}
}
主要区别在于,在我们绘制刻度线的循环中,我们不是计算“间隙”值,而是为每个步骤获取“拇指中心”:
for i in 0...stepCount {
// get center of Thumb at each "step"
let aThumbR: CGRect = self.thumbRect(forBounds: bounds, trackRect: trackR, value: Float(i) / Float(stepCount))
pt.x = aThumbR.midX
pth.move(to: pt)
pth.addLine(to: CGPoint(x: pt.x, y: pt.y + tickHeight))
}
这是使用自定义拇指图像的图像,步骤 0、1 和 2:
还有我用于拇指的@2x (62x62) 和@3x (93x93) 图像: