点击 UISlider 来设置数值。

29

我创建了一个滑块(作为视频控制,就像YouTube底部的控制条),并设置了最大值(视频总时长)和最小值。然后使用SeekToTime更改当前时间。现在,用户可以滑动滑块以更改值。

我想实现的是让用户在滑块上任何位置轻触即可设置视频的当前时间。

我从这个答案中得到了一种方法,并尝试将其应用到我的情况中,但无法使其正常工作。

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()函数。

我做错了什么?或者有更好的方法来实现我想要的效果吗?


1
你是否忘记设置手势识别器的代理? - whatever0010011
9个回答

47

看起来你需要在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)
    }
}

这个答案比我找到的其他任何答案都要好得多!谢谢! - kenjikato
action: "sliderTapped:" 的创建在 Xcode 8.2.1 中已不再有效。我尝试了 Xcode 给我的建议,但仍然失败。有其他人解决了吗? - Zonker.in.Geneva
2
我想我找到了。对于Swift 3,代码如下:let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.tapBlurButton(_:))) - Zonker.in.Geneva
3
滑块条两侧各有15像素的“死区”(滑块不会继续移动一旦到达该“死区”),因此您应该将positionOfSlider.x增加15,并将widthOfSlider减少30。请注意,这需要考虑到落在滑块值范围之外的轻敲,即用户在任一“死区”中轻敲的情况。如果您正在使用“捕捉”到整数值的滑块,则应使用slider.setValue(Float(Int(newValue + 0.5)))或者对最接近的整数四舍五入以设置新值。 - DivideByZer0
2
代码运行良好,但没有考虑滑块的最小值是否为零。以下是正确的代码:let newValue = (((pointTapped.x - positionOfSlider.x) / widthOfSlider) * CGFloat(distanceSlider.maximumValue)) + ((1 - ((pointTapped.x - positionOfSlider.x) / widthOfSlider)) * CGFloat(distanceSlider.minimumValue)) - Paintoshi
1
小修正:let pointTapped = gestureRecognizer.location(in: slider.superView) - Irshad Mohamed

40

似乎只需继承UISlider类并始终返回true到beginTracking函数就可以产生所需的效果。

iOS 10和Swift 3

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

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


3
简洁明了的答案。这应该成为被接受的答案。 - Sylvan D Ash
1
如果您为滑块编写了自定义代码,例如在手指释放时强制离散步骤,则此代码也将在单击时调用该函数。也许这不是期望的效果,需要使用myuiviews中提供的答案。但在大多数情况下,这将正常工作! - Paintoshi
终于苹果了 :) - shelll
1
我不明白为什么这是一个答案。我按照描述的方式实现了它,但在滑块被点击时并没有发生任何神奇的事情(除了调用值更改事件)。 - matt

4

我喜欢这种方法

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

为了计算百分比,我建议使用以下代码行:let percent = slider.minimumValue + Float(location.x / slider.bounds.width) * (slider.maximumValue - slider.minimumValue) - Raoul

3
我实现了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 
    }

我随后重写了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
    }

希望它有些用处,且符合你的需求。

2
这是我的代码,基于“myuiviews”的答案。
我修复了原始代码中的两个小“bug”。

1-点击0太困难了,所以我让它更容易
2-滑动滑块的拇指只要稍微一点就会触发“tapGestureRecognizer”,使其返回到初始位置,因此我添加了一个最小距离过滤器以避免这种情况。

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

我对newValue的计算进行了一点改动,以便在滑块的最小值不为零时进行适配。 'code'newValue = (((pointTapped.x - positionOfSlider.x) * CGFloat(self.offerSliderView.maximumValue - self.offerSliderView.minimumValue) / widthOfSlider) + CGFloat(self.offerSliderView.minimumValue))'code' - Jean-Paul Manuel

0

pteofil的答案应该在这里被接受。

以下是Xamarin的解决方案,供大家参考:

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

正如 pteofil 所提到的,您需要为此子类化 UISlider。


5
你的问题是:你的回答有什么新内容?我的翻译是:你的回答有什么新内容? - artgb

0

因此,将问题视为一个滑块,其值可以通过滑动"拇指"或在整个滑块上轻击来改变,并汇总本页上的许多答案和评论,我们可以将解决方案简洁地表达如下:

class MySlider: UISlider {
    override init(frame: CGRect) {
        super.init(frame:frame)
        config()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        config()
    }

    func config() {
        let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
        addGestureRecognizer(tap)
    }

    @objc func tapped(_ tapper: UITapGestureRecognizer) {
        let pointTappedX = tapper.location(in: self).x
        let positionOfSliderX = bounds.origin.x + 15
        let widthOfSlider = bounds.size.width - 30
        let newX = (pointTappedX - positionOfSliderX) / widthOfSlider

        let newValue = newX * CGFloat(maximumValue) + (1 - newX) * CGFloat(minimumValue)
        setValue(Float(newValue), animated: true)
        sendActions(for: .valueChanged)
    }
}

添加了轻触手势后,您无法在滑块上接收滑动事件,反之亦然。 - undefined

0
对我来说,@pietor的答案只有在触摸移动足够触发值变化时才有效。这在实际设备上可能是正确的,但在模拟器上,它对单个触摸事件没有任何作用。下面的代码对我来说效果更好,当滑块移动到触摸位置时更加平滑。
class CustomSlider : UISlider
{
  override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
    let track = trackRect(forBounds: bounds)
    let thumb = thumbRect(forBounds: bounds, trackRect: track, value: 0.0)
    let pos = touch.location(in: self)
    let normalizedValue = Float((pos.x - track.minX - thumb.width / 2.0) / (track.width - thumb.width))
    self.setValue(minimumValue + (maximumValue - minimumValue) * normalizedValue, animated: true)
    return true
  }
}

-1
可能最简单的解决方案是,使用“touch up inside”操作,通过接口生成器连接。
@IBAction func finishedTouch(_ sender: UISlider) {

    finishedMovingSlider(sender)
}

当你的手指离开手机屏幕时,这个函数将会被调用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接