点击 UISlider 以设置值

Tap on UISlider to Set the Value

我创建了一个滑块(用作视频控件,就像底部的 YouTube)并设置了最大值(持续时间)和最小值。然后使用 SeekToTime 更改当前时间。现在,用户可以滑动滑块的拇指来改变值

我想要实现的是让用户点击滑块上的任意位置并设置视频的当前时间。

我从 this answer 那里得到了一个方法,我试图将它应用到我的案例中,但无法成功

class ViewController: UIViewController, PlayerDelegate {

var slider: UISlider!

override func viewDidLoad() {
  super.viewDidLoad()

  // Setup the slider
}

func sliderTapped(gestureRecognizer: UIGestureRecognizer) {
  //  print("A")

  let pointTapped: CGPoint = gestureRecognizer.locationInView(self.view)

  let positionOfSlider: CGPoint = slider.frame.origin
  let widthOfSlider: CGFloat = slider.frame.size.width
  let newValue = ((pointTapped.x - positionOfSlider.x) * CGFloat(slider.maximumValue) / widthOfSlider)

  slider.setValue(Float(newValue), animated: true)      
 }
}

我认为这应该可行,但运气不好。然后我尝试通过尝试将 "A" 打印到日志来进行调试,但显然它没有进入 sliderTapped() 函数。

我做错了什么?或者是否有更好的方法来实现我想要实现的目标?

看起来您需要按照上面的代码示例在 viewDidLoad() 中实际初始化点击手势识别器。那里有一条评论,但我没有看到任何地方正在创建识别器。

Swift 2:

class ViewController: UIViewController {

    var slider: UISlider!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Setup the slider

        // Add a gesture recognizer to the slider
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "sliderTapped:")
        self.slider.addGestureRecognizer(tapGestureRecognizer)
    }

    func sliderTapped(gestureRecognizer: UIGestureRecognizer) {
        //  print("A")

        let pointTapped: CGPoint = gestureRecognizer.locationInView(self.view)

        let positionOfSlider: CGPoint = slider.frame.origin
        let widthOfSlider: CGFloat = slider.frame.size.width
        let newValue = ((pointTapped.x - positionOfSlider.x) * CGFloat(slider.maximumValue) / widthOfSlider)

        slider.setValue(Float(newValue), animated: true)      
    }
}

Swift 3:

class ViewController: UIViewController {

    var slider: UISlider!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Setup the slider

        // Add a gesture recognizer to the slider
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(sliderTapped(gestureRecognizer:)))
        self.slider.addGestureRecognizer(tapGestureRecognizer)
    }

    func sliderTapped(gestureRecognizer: UIGestureRecognizer) {
        //  print("A")

        let pointTapped: CGPoint = gestureRecognizer.location(in: self.view)

        let positionOfSlider: CGPoint = slider.frame.origin
        let widthOfSlider: CGFloat = slider.frame.size.width
        let newValue = ((pointTapped.x - positionOfSlider.x) * CGFloat(slider.maximumValue) / widthOfSlider)

        slider.setValue(Float(newValue), animated: true)
    }
}

似乎只是子类化 UISlider 并始终返回 true 到 beginTracking 就产生了预期的效果。

iOS 10 和 Swift 3

class CustomSlider: UISlider {
    override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
        return true
    }
}

之后在您的代码中使用 CustomSlider 而不是 UISlider。

pteofil 的答案应该是这里接受的答案。

下面是 Xamarin 的解决方案,供大家感兴趣:

public override bool BeginTracking(UITouch uitouch, UIEvent uievent)
{
    return true;
}

如 pteofil 所述,您需要子类化 UISlider 才能使其正常工作。

这是我的代码,基于 "myuiviews" 答案。
我修复了原始代码的 2 小"bugs"。

1 - 轻敲 0 太难了,所以我让它更容易了
2 - 稍微滑动滑块的拇指也会触发 "tapGestureRecognizer",这使得它 return 到初始位置,所以我添加了一个最小距离过滤器来避免这种情况。

Swift 4

class ViewController: UIViewController {

    var slider: UISlider!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Setup the slider

        // Add a gesture recognizer to the slider
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(sliderTapped(gestureRecognizer:)))
        self.slider.addGestureRecognizer(tapGestureRecognizer)
    }

    @objc func sliderTapped(gestureRecognizer: UIGestureRecognizer) {
        //  print("A")

        var pointTapped: CGPoint = gestureRecognizer.location(in: self.view)
        pointTapped.x -= 30  //Subtract left constraint (distance of the slider's origin from "self.view" 

        let positionOfSlider: CGPoint = slider.frame.origin
        let widthOfSlider: CGFloat = slider.frame.size.width

        //If tap is too near from the slider thumb, cancel
        let thumbPosition = CGFloat((slider.value / slider.maximumValue)) * widthOfSlider
        let dif = abs(pointTapped.x - thumbPosition)
        let minDistance: CGFloat = 51.0  //You can calibrate this value, but I think this is the maximum distance that tap is recognized
        if dif < minDistance { 
            print("tap too near")
            return
        }

        var newValue: CGFloat
        if pointTapped.x < 10 {
            newValue = 0  //Easier to set slider to 0
        } else {
            newValue = ((pointTapped.x - positionOfSlider.x) * CGFloat(slider.maximumValue) / widthOfSlider)
        }



        slider.setValue(Float(newValue), animated: true)
    }
}

可能最简单的解决方案是使用 "touch up inside" 操作,通过界面构建​​器连接。

@IBAction func finishedTouch(_ sender: UISlider) {

    finishedMovingSlider(sender)
}

一旦您的手指离开 phone 屏幕,就会调用此方法。

我喜欢这种方法

extension UISlider {
    public func addTapGesture() {
        let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        addGestureRecognizer(tap)
    }

    @objc private func handleTap(_ sender: UITapGestureRecognizer) {
        let location = sender.location(in: self)
        let percent = minimumValue + Float(location.x / bounds.width) * maximumValue
        setValue(percent, animated: true)
        sendActions(for: .valueChanged)
    }
}

稍后再打电话

let slider = UISlider()
slider.addTapGesture()

我实现了 pteofil,但是,因为我已经有一个附加到 valueChanged 的操作,我在跟踪方面遇到了问题。

我在看 Adam 的回答,我注意到他也做了一个非常好的实现,除了我通常不喜欢添加手势识别器。所以我结合了两个答案(有点),现在我都可以使用滑动来更改值(因此触发 valueChanged 以及如果点击滑块:

  1. 我对 UISlider 进行了子类化,并覆盖了 beginTracking 方法:
    override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
        return true //Without this, we don't get any response in the method below 
    }
  1. 然后我覆盖了 touchEnded 方法(我猜你也可以覆盖其他状态,但这似乎是最合乎逻辑的)
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let location = touch.location(in: self)
        let conversion = minimumValue + Float(location.x / bounds.width) * maximumValue
        setValue(conversion, animated: false)
        sendActions(for: .valueChanged) //Add this because without this it won't work.
    }

我在其他地方也有我的标准 IBAction(这与上面没有直接关系,但我添加它是为了提供完整的上下文):

    @IBAction func didSkipToTime(_ sender: UISlider) {
        print("sender value: \(sender.value)") // Do whatever in this method
    }

希望它能对您有所帮助并满足您的需要。