Подтвердить что ты не робот

Как включить "коснуться и сдвинуть" в UISlider?

То, что я хочу получить, - это UISlider, который позволяет пользователю не только сползать, когда он запускается на thumbRect, но также и когда он вступает в другое место. Когда пользователь нажимает на ползунок, но за пределами thumbRect, ползунок должен перейти к этому значению , а затем все еще придерживаться скольжения пользователя.

То, что я пробовал до сих пор, заключается в реализации подкласса UIGestureRecognizer, такого как в этом предложении. Он начинается прямо тогда, когда происходит касание где-то вне thumbRect. Проблема заключается в том, что слайдер устанавливает значение , но затем дальнейшие сдвиговые жесты игнорируются, потому что распознающий сенсорный захват украл прикосновение.

Как я могу реализовать слайдер, где вы можете нажимать в любом месте, но все же слайд сразу?


Изменить: ali59a так любезно добавил пример того, что я сделал сейчас. Для этого нужно снова поднять палец, после чего я могу прикоснуться и перетащить на слайд (крана не то, что я хочу, мне нужно "прикоснуться и сдвинуть" сразу).

4b9b3361

Ответ 1

Я не уверен, что вы все еще ищете ответ для этого, но я просто смотрел на это сам сегодня; и мне удалось заставить его работать для меня.

Ключ к нему использует UILongPressGestureRecognizer вместо UITapGestureRecognizer, тогда мы можем установить minimumPressDuration распознавателя на 0; что делает его действительным в качестве распознавателя крана, за исключением того, что вы можете теперь фактически проверить его состояние.

Полагая, что предложенный ali59a будет работать для вас, просто заменив UITapGestureRecognizer на UILongPressGestureRecognizer. Тем не менее, я обнаружил, что это, похоже, не помещало thumbRect прямо под моим пальцем. Мне это показалось немного.

Я создал свой собственный подкласс UISlider для своего проекта, и вот как я применил для него функцию "tap and slide".

В моем методе 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];
}

Я надеюсь, что это имеет смысл и помогает вам.

Ответ 2

Я преобразовал ответ, предоставленный DWilliames, в Swift

Внутри вашего видаDidAppear()

let longPress                  = UILongPressGestureRecognizer(target: self.slider, action: Selector("tapAndSlide:"))
longPress.minimumPressDuration = 0
self.addGestureRecognizer(longPress)

Файл класса

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

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

Ответ 3

Вы должны добавить жестов кран на 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

Ответ 4

Здесь моя модификация выше:

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

Ответ 5

Добавление быстрой версии 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)
    }
}

Ответ 6

Я завершил решение @DWilliames для подкласса UISlider, содержащего minimum и maximumValueImages.

Кроме того, я реализовал функциональность для пользовательских касаний в областях вне trackArea (означает либо область вокруг minimum, либо maximumValueImage). Прикосновение к этим областям перемещает слайдер/изменяет значение в интервалах.

- (void) tapAndSlide: (UILongPressGestureRecognizer*) gesture {
    CGPoint touchPoint = [gesture locationInView: self];
    CGRect trackRect = [self trackRectForBounds: self.bounds];
    CGFloat thumbWidth = [self thumbRectForBounds: self.bounds trackRect: trackRect value: self.value].size.width;
    CGRect trackArea = CGRectMake(trackRect.origin.x, 0, trackRect.size.width, self.bounds.size.height);
    CGFloat value;

if (CGRectContainsPoint(trackArea, touchPoint)) {
    if (touchPoint.x <= trackArea.origin.x + thumbWidth/2.0) {
        value = self.minimumValue;
    }
    else if (touchPoint.x >= trackArea.origin.x + trackArea.size.width - thumbWidth/2.0) {
        value = self.maximumValue;
    }
    else {
        CGFloat percentage = (touchPoint.x - trackArea.origin.x - thumbWidth/2.0)/(trackArea.size.width - thumbWidth);
        CGFloat delta = percentage*(self.maximumValue - self.minimumValue);
        value = self.minimumValue + delta;
    }

    if (value != self.value) {
        if (gesture.state == UIGestureRecognizerStateBegan) {
            [UIView animateWithDuration: 0.2 delay: 0 options: UIViewAnimationOptionCurveEaseInOut animations: ^{
                [self setValue: value animated: YES];

            } completion: ^(BOOL finished) {
                [self sendActionsForControlEvents: UIControlEventValueChanged];
            }];
        }
        else {
            [self setValue: value animated: YES];
            [self sendActionsForControlEvents: UIControlEventValueChanged];
        }
    }
}
else {
    if (gesture.state == UIGestureRecognizerStateBegan) {
        if (touchPoint.x <= trackArea.origin.x) {
            if (self.value == self.minimumValue) return;
            value = self.value - 1.5;
        }
        else {
            if (self.value == self.maximumValue) return;
            value = self.value + 1.5;
        }
        CGFloat duration = 0.1;
        [UIView animateWithDuration: duration delay: 0 options: UIViewAnimationOptionCurveEaseInOut animations: ^{
            [self setValue: value animated: YES];
        } completion: ^(BOOL finished) {
            [self sendActionsForControlEvents: UIControlEventValueChanged];
        }];
    }
}
}

Ответ 7

Обновлен ответ 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 any
        // custom behaviour going on there and pass the slider
        // variable as the parameter, as indicated below
        self.sliderValueChanged(slider)
    }
}

Ответ 8

Мое решение довольно просто:

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

Ответ 9

Я не проверял ответ Дэвида Уильямса , но я опубликую свое решение, если кто-то ищет другой способ сделать это.

Swift 4

Сначала создайте пользовательский UISlider, чтобы он обнаруживал штрихи на панели:

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

(не забудьте установить ползунок для этого CustomSlider, на раскадровке)

В представлении 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
        }
    }
}

И в вашем селекторном методе "sliderTap":

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

Предложение: установите плеер "currentTime" перед началом воспроизведения:

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

Ответ 10

Рискуя быть наказанным чистым сообществом iOS...

Вот решение для Xamarin iOS С#, преобразованного из ответа Дэвида Уильямса.

Sub class 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 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);
        }

    }

}

Ответ 11

Чтобы расширить ответ Кханга Azun- для swift 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
}