将自动收报机添加到捕捉 UISlider
Add ticker to snapping UISlider
我在创建自定义 UISlider 时遇到一些问题,该自定义 UISlider 在某些值处捕捉,并且在这些值处也有刻度线。我有捕捉部分工作,但刻度线是一个问题。滑块卡在 12 个位置,从 15-70 每 5 个。我尝试了多种方法,包括将 12 个子视图添加到堆栈视图并将其放置在滑块上,还计算了每个子视图之间的步长。这两种方法都将刻度线放在拇指卡住的地方。有谁知道如何正确执行此操作?
这是第二种方法:
let stepCount = 12
print("THE WIDTH: \(self.bounds.width)")
guard let imageWidth = self.currentThumbImage?.size.width else {return}
guard let imageWidth = self.currentThumbImage?.size.width else {return}
let stepWidth = bounds.width / CGFloat(stepCount)
for i in 0..<stepCount {
let view = UIView(frame: CGRect(x: stepWidth / 2 + stepWidth * CGFloat(i) - imageWidth / 2, y: 0, width: 1, height: 29))
view.backgroundColor = .lightGray
self.insertSubview(view, at: 0)
}
这是捕捉代码:
@objc func valueChanged(_ sender: UISlider){
let step: Float = 5
let roundedValue = round(sender.value / step) * step
self.value = roundedValue
delegate?.sliderChanged(Int(roundedValue), sender)
}
您需要处理各种宽度问题。
首先,看看这三个“默认”UISlider
控件。拇指垂直偏移所以我们可以看到轨道,红色虚线轮廓是滑块框架:
- 最上面的是最小值(最左边)
- 中间那个是50%
- 底部的那个是最大值(最右边)
我们可以看到,拇指的水平 center NOT 在轨道矩形的结束(边界)。
如果我们希望刻度线与拇指的水平中心对齐,我们需要根据拇指中心的原点和宽度在最小值和最大值处计算 x 位置:
下一个问题是您可能不希望轨道矩形/图像延伸到刻度线的 left/right。
那样的话,我们需要清除内置的轨迹图,自己绘制:
下面是一些您可以尝试使用的示例代码:
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) 图像:
我在创建自定义 UISlider 时遇到一些问题,该自定义 UISlider 在某些值处捕捉,并且在这些值处也有刻度线。我有捕捉部分工作,但刻度线是一个问题。滑块卡在 12 个位置,从 15-70 每 5 个。我尝试了多种方法,包括将 12 个子视图添加到堆栈视图并将其放置在滑块上,还计算了每个子视图之间的步长。这两种方法都将刻度线放在拇指卡住的地方。有谁知道如何正确执行此操作?
这是第二种方法:
let stepCount = 12
print("THE WIDTH: \(self.bounds.width)")
guard let imageWidth = self.currentThumbImage?.size.width else {return}
guard let imageWidth = self.currentThumbImage?.size.width else {return}
let stepWidth = bounds.width / CGFloat(stepCount)
for i in 0..<stepCount {
let view = UIView(frame: CGRect(x: stepWidth / 2 + stepWidth * CGFloat(i) - imageWidth / 2, y: 0, width: 1, height: 29))
view.backgroundColor = .lightGray
self.insertSubview(view, at: 0)
}
这是捕捉代码:
@objc func valueChanged(_ sender: UISlider){
let step: Float = 5
let roundedValue = round(sender.value / step) * step
self.value = roundedValue
delegate?.sliderChanged(Int(roundedValue), sender)
}
您需要处理各种宽度问题。
首先,看看这三个“默认”UISlider
控件。拇指垂直偏移所以我们可以看到轨道,红色虚线轮廓是滑块框架:
- 最上面的是最小值(最左边)
- 中间那个是50%
- 底部的那个是最大值(最右边)
我们可以看到,拇指的水平 center NOT 在轨道矩形的结束(边界)。
如果我们希望刻度线与拇指的水平中心对齐,我们需要根据拇指中心的原点和宽度在最小值和最大值处计算 x 位置:
下一个问题是您可能不希望轨道矩形/图像延伸到刻度线的 left/right。
那样的话,我们需要清除内置的轨迹图,自己绘制:
下面是一些您可以尝试使用的示例代码:
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) 图像: