簡體   English   中英

為捕捉 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%
  • 底部是最大值(最右邊)

正如我們所看到的,拇指的水平中心不是在跟蹤矩形的末端(邊界)。

如果我們希望刻度線與拇指的水平中心對齊,我們需要根據拇指中心的原點和寬度在最小值和最大值處計算 x 位置:

在此處輸入圖片說明

下一個問題是您可能不希望軌道矩形/圖像延伸到刻度線的左側/右側。

在這種情況下,我們需要清除內置的軌道圖像並繪制我們自己的:

在此處輸入圖片說明

以下是一些您可以嘗試使用的示例代碼:

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

注意:這只是此任務的一種方法(僅示例代碼)。 搜索會發現您可能想要查看的許多其他方法/示例/等。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM