如何在UISlider中启用“点按和滑动”?

时间:2014-03-28 15:36:08

标签: ios uigesturerecognizer uislider uitapgesturerecognizer

我想得到的是UISlider,它不仅可以让用户在thumbRect开始时滑动,也可以在其他地方点击时滑动。当用户点击滑块但在thumbRect之外时,滑块应跳转到该值,然后仍然保持用户的滑动手势

到目前为止我尝试过的是实现像in this suggestion这样的UIGestureRecognizer的子类。它会在thumbRect之外的某处发生触碰时启动。问题是滑块设置其值但是由于触摸识别器已经触摸了,因此忽略了进一步的滑动手势。

我如何实现一个滑块,您可以在任何地方点按但仍然可以立即滑动?


编辑:ali59a非常友好地添加我现在所做的an example。这需要再次抬起手指,之后我可以触摸并拖动滑动(水龙头不是我想要的,我需要立即'触摸并滑动'。

13 个答案:

答案 0 :(得分:23)

我不确定你是否还在寻找答案,但我今天只是在看这个;我设法让它为我工作。

它的关键是使用UILongPressGestureRecognizer而不只是UITapGestureRecognizer,我们可以将识别器的minimumPressDuration设置为0;使它充当点击识别器,除了你现在可以实际检查它的状态。

只需将UITapGestureRecognizer替换为UILongPressGestureRecognizer,将所建议的ali59a设为适合您。但是,我发现这似乎并没有将thumbRect直接放在我的拇指下面。它对我来说有点不合适。

我为我的项目创建了自己的UISlider子类,这就是我为我实现“点击和滑动功能”的方法。

在我的init方法中:

UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(tapAndSlide:)];
longPress.minimumPressDuration = 0;
[self addGestureRecognizer:longPress];

然后我的tapAndSlide:方法:

- (void)tapAndSlide:(UILongPressGestureRecognizer*)gesture
{
    CGPoint pt = [gesture locationInView: self];
    CGFloat thumbWidth = [self thumbRect].size.width;
    CGFloat value;

    if(pt.x <= [self thumbRect].size.width/2.0)
        value = self.minimumValue;
    else if(pt.x >= self.bounds.size.width - thumbWidth/2.0)
        value = self.maximumValue;
    else {
        CGFloat percentage = (pt.x - thumbWidth/2.0)/(self.bounds.size.width - thumbWidth);
        CGFloat delta = percentage * (self.maximumValue - self.minimumValue);
        value = self.minimumValue + delta;
    }

    if(gesture.state == UIGestureRecognizerStateBegan){
        [UIView animateWithDuration:0.35 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
            [self setValue:value animated:YES];
            [super sendActionsForControlEvents:UIControlEventValueChanged];
        } completion:nil];
    }
    else [self setValue:value];

    if(gesture.state == UIGestureRecognizerStateChanged)
        [super sendActionsForControlEvents:UIControlEventValueChanged];
}

我还使用了一种方法来返回自定义thumbRect的框架:

- (CGRect)thumbRect {
    CGRect trackRect = [self trackRectForBounds:self.bounds];
    return [self thumbRectForBounds:self.bounds trackRect:trackRect value:self.value];
}

我的滑块动画显示用户首次点击的位置,超过0.35秒。我认为这看起来很甜蜜,所以我把它包含在那个代码中。 如果您不想这样,只需尝试:

- (void)tapAndSlide:(UILongPressGestureRecognizer*)gesture
{
    CGPoint pt = [gesture locationInView: self];
    CGFloat thumbWidth = [self thumbRect].size.width;
    CGFloat value;

    if(pt.x <= [self thumbRect].size.width/2.0)
        value = self.minimumValue;
    else if(pt.x >= self.bounds.size.width - thumbWidth/2.0)
        value = self.maximumValue;
    else {
        CGFloat percentage = (pt.x - thumbWidth/2.0)/(self.bounds.size.width - thumbWidth);
        CGFloat delta = percentage * (self.maximumValue - self.minimumValue);
        value = self.minimumValue + delta;
    }

    [self setValue:value];

    if(gesture.state == UIGestureRecognizerStateChanged)
        [super sendActionsForControlEvents:UIControlEventValueChanged];
}

我希望这有意义,并帮助你。

答案 1 :(得分:4)

您应该在UISlider上添加点按手势。

例如:

 UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sliderTapped:)];
    [_slider addGestureRecognizer:tapGestureRecognizer];

在sliderTapped中,您可以获取位置并更新滑块的值:

- (void)sliderTapped:(UIGestureRecognizer *)gestureRecognizer {
    CGPoint  pointTaped = [gestureRecognizer locationInView:gestureRecognizer.view];
    CGPoint positionOfSlider = _slider.frame.origin;
    float widthOfSlider = _slider.frame.size.width;
    float newValue = ((pointTaped.x - positionOfSlider.x) * _slider.maximumValue) / widthOfSlider;
    [_slider setValue:newValue];
}

我在这里创建了一个示例:https://github.com/ali59a/tap-and-slide-in-a-UISlider

答案 2 :(得分:3)

这是我对上述内容的修改:

class TapUISlider: UISlider {

  func tapAndSlide(gesture: UILongPressGestureRecognizer) {
    let pt = gesture.locationInView(self)
    let thumbWidth = self.thumbRect().size.width
    var value: Float = 0

    if (pt.x <= self.thumbRect().size.width / 2) {
      value = self.minimumValue
    } else if (pt.x >= self.bounds.size.width - thumbWidth / 2) {
      value = self.maximumValue
    } else {
      let percentage = Float((pt.x - thumbWidth / 2) / (self.bounds.size.width - thumbWidth))
      let delta = percentage * (self.maximumValue - self.minimumValue)
      value = self.minimumValue + delta
    }

    if (gesture.state == UIGestureRecognizerState.Began) {
      UIView.animateWithDuration(0.35, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut,
        animations: {
          self.setValue(value, animated: true)
          super.sendActionsForControlEvents(UIControlEvents.ValueChanged)
        }, completion: nil)
    } else {
      self.setValue(value, animated: false)
      super.sendActionsForControlEvents(UIControlEvents.ValueChanged)
    }
  }

  func thumbRect() -> CGRect {
    return self.thumbRectForBounds(self.bounds, trackRect: self.bounds, value: self.value)
  }
}

答案 3 :(得分:3)

添加快速版的Ali AB。答案,

@IBAction func sliderTappedAction(sender: UITapGestureRecognizer)
{
    if let slider = sender.view as? UISlider {

        if slider.highlighted { return }

        let point = sender.locationInView(slider)
        let percentage = Float(point.x / CGRectGetWidth(slider.bounds))
        let delta = percentage * (slider.maximumValue - slider.minimumValue)
        let value = slider.minimumValue + delta
        slider.setValue(value, animated: true)
    }
}

答案 4 :(得分:1)

我为包含UISliderminimum的{​​{1}}子类完成了@DWilliames解决方案。

此外,我在maximumValueImages之外的区域实现了用户触摸功能(意味着trackAreaminimum周围的区域)。触摸这些区域会移动滑块/以间隔更改值。

maximumValueImage

答案 5 :(得分:0)

我没有查看David Williames answer,但我会发布我的解决方案,以防有人正在寻找另一种方法。

Swift 4

首先创建一个自定义UISlider,以便它也能检测到条形图上的触摸:

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

(不要忘记在故事板上将滑块设置为此CustomSlider)

显示滑块的视图控制器的on viewDidLoad:

self.slider.addTarget(self, action: #selector(sliderTap), for: .touchDown)

(这仅用于在移动滑块时暂停播放器)

然后,在您的UISlider操作上:

@IBAction func moveSlider(_ sender: CustomSlider, forEvent event: UIEvent) {
    if let touchEvent = event.allTouches?.first {
        switch touchEvent.phase {
            case .ended, .cancelled, .stationary:
                //here, start playing if needed
                startPlaying()                    
            default:
                break
        }
    }
}

在你的&#34; sliderTap&#34;选择器方法:

@objc func sliderTap() {
    //pause the player, if you want
    audioPlayer?.pause()
}

建议:设置播放器&#34; currentTime&#34;在开始玩之前:

private func startPlaying() {
    audioPlayer?.currentTime = Double(slider.value)
    audioPlayer?.play()
}

答案 6 :(得分:0)

更新 tsji10dra Swift 4 的回答:

@IBAction func sliderTappedAction(sender: UITapGestureRecognizer) {

    if let slider = sender.view as? UISlider {
        if slider.isHighlighted { return }

        let point = sender.location(in: slider)
        let percentage = Float(point.x / slider.bounds.size.width)
        let delta = percentage * (slider.maximumValue - slider.minimumValue)
        let value = slider.minimumValue + delta
        slider.setValue(value, animated: true)

        // also remember to call valueChanged if there's any
        // custom behaviour going on there and pass the slider
        // variable as the parameter, as indicated below
        self.sliderValueChanged(slider)
    }
}

答案 7 :(得分:0)

我的解决方案很简单:

class CustomSlider: UISlider {
    override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
        let newValue = <calculated_value>
        self.setValue(newValue, animated: false)
        super.sendActions(for: UIControlEvents.valueChanged)
        return true
}}

答案 8 :(得分:0)

有被iOS纯粹社区追逐的风险...

这是从David Williames Answer转换而来的Xamarin iOS C#解决方案。

子类UISlider:

[Register(nameof(UISliderCustom))]
[DesignTimeVisible(true)]
public class UISliderCustom : UISlider
{

    public UISliderCustom(IntPtr handle) : base(handle) { }

    public UISliderCustom()
    {
        // Called when created from code.
        Initialize();
    }

    public override void AwakeFromNib()
    {
        // Called when loaded from xib or storyboard.
        Initialize();
    }

    void Initialize()
    {
        // Common initialization code here.

        var longPress = new UILongPressGestureRecognizer(tapAndSlide);
        longPress.MinimumPressDuration = 0;
        //longPress.CancelsTouchesInView = false;
        this.AddGestureRecognizer(longPress);
        this.UserInteractionEnabled = true;

    }

    private void tapAndSlide(UILongPressGestureRecognizer gesture)
    {
        System.Diagnostics.Debug.WriteLine($"{nameof(UISliderCustom)} RecognizerState {gesture.State}");

        // need to propagate events down the chain
        // I imagine iOS does something similar
        // for whatever recogniser on the thumb control
        // It's not enough to set CancelsTouchesInView because
        // if clicking on the track away from the thumb control
        // the thumb gesture recogniser won't pick it up anyway
        switch (gesture.State)
        {
            case UIGestureRecognizerState.Cancelled:
                this.SendActionForControlEvents(UIControlEvent.TouchCancel);
                break;

            case UIGestureRecognizerState.Began:
                this.SendActionForControlEvents(UIControlEvent.TouchDown);
                break;

            case UIGestureRecognizerState.Changed:
                this.SendActionForControlEvents(UIControlEvent.ValueChanged);                    
                break;

            case UIGestureRecognizerState.Ended:
                this.SendActionForControlEvents(UIControlEvent.TouchUpInside);
                break;

            case UIGestureRecognizerState.Failed:
                //?
                break;

            case UIGestureRecognizerState.Possible:
                //?
                break;

        }

        var pt = gesture.LocationInView(this);
        var thumbWidth = CurrentThumbImage.Size.Width;
        var value = 0f;

        if (pt.X <= thumbWidth / 2)
        {
            value = this.MinValue;
        }
        else if (pt.X >= this.Bounds.Size.Width - thumbWidth / 2)
        {
            value = this.MaxValue;
        }
        else
        {
            var percentage = ((pt.X - thumbWidth / 2) / (this.Bounds.Size.Width - thumbWidth));
            var delta = percentage * (this.MaxValue - this.MinValue);
            value = this.MinValue + (float)delta;
        }

        if (gesture.State == UIGestureRecognizerState.Began)
        {               
            UIView.Animate(0.35, 0, UIViewAnimationOptions.CurveEaseInOut,
                () =>
                {
                    this.SetValue(value, true);
                },
                null);
        }
        else
        {
            this.SetValue(value, animated: false);
        }

    }

}

答案 9 :(得分:0)

要扩展Khang Azun的答案(快速5),请在UISlider自定义类中添加以下内容:

override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
    let percent = Float(touch.location(in: self).x / bounds.size.width)
    let delta = percent * (maximumValue - minimumValue)

    let newValue = minimumValue + delta
    self.setValue(newValue, animated: false)
    super.sendActions(for: UIControl.Event.valueChanged)
    return true
}

答案 10 :(得分:0)

从Apple,

https://developer.apple.com/forums/thread/108317

现在,这在iOS 10和iOS 11上可以正常使用。您可以像往常一样滑动,由于上面的代码,您可以点击滑块,它会自动滑动。但是,在iOS 12中,此操作无效。您必须强行触摸它才能点击以工作

答案 11 :(得分:0)

这是我有效的解决方案:

update(BitFlags::Read | BitFlags::Write);

答案 12 :(得分:0)

这在iOS 13.6和14.0中对我有效
无需添加仅手势即可覆盖beginTracking这样的功能:

@objc
private func sliderTapped(touch: UITouch) {
    let point = touch.location(in: self)
    let percentage = Float(point.x / self.bounds.width)
    let delta = percentage * (self.maximumValue - self.minimumValue)
    let newValue = self.minimumValue + delta
    if newValue != self.value {
        value = newValue
        sendActions(for: .valueChanged)
    }
}

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