web-dev-qa-db-ja.com

UISliderで「タップアンドスライド」を有効にするにはどうすればよいですか?

私が取得したいのはUISliderです。これにより、ユーザーはthumbRectを開始したときだけでなく、他の場所をタップしたときにもスライドできます。ユーザーがスライダーをタップしたが、thumbRectの外側にある場合、スライダーはその値にジャンプし、ユーザーのスライドジェスチャ

私がこれまでに試したことは、 この提案では のようなUIGestureRecognizerのサブクラスを実装することでした。 thumbRectの外側のどこかでタッチダウンが発生するとすぐに開始します。問題は、スライダーがその値を設定することですが、タッチダウンレコグナイザーがタッチを盗んだため、それ以上のスライドジェスチャは無視されます。

どこでもタップしてもすぐにスライドできるスライダーを実装するにはどうすればよいですか?


編集:ALi59aはとても親切に追加してくれました 私が今やったことの。これには、もう一度指を離す必要があります。その後、タッチしてドラッグしてスライドできます(タップは必要なものではなく、すぐに「タッチアンドスライド」が必要です)。

18
Norbert

あなたがまだこれに対する答えを探しているかどうかはわかりませんが、私は今日これを自分で見ていました。そして私はそれを私のために動かすことができました。

その鍵は、UILongPressGestureRecognizerだけでなくUITapGestureRecognizerを使用することです。次に、レコグナイザーのminimumPressDurationを0に設定できます。実際に状態を確認できるようになった以外は、タップ認識機能として機能します。

UITapGestureRecognizerUILongPressGestureRecognizerに置き換えるだけで、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];
}

それが理にかなっていて、あなたを助けてくれることを願っています。

23
David Williames

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

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
Ali Abbas

上記に対する私の変更は次のとおりです。

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

SwiftバージョンのALiAB。の回答を追加します。

@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)
    }
}
3
itsji10dra

Swift 4に対するtsji10draの回答を更新しました:

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

私の解決策は非常に簡単です:

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
}}
1
Khang Azun

UISliderminimumを含むmaximumValueImagesサブクラスの@DWilliamesソリューションを完成させました。

さらに、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];
        }];
    }
}
}
1
anneblue

Khang Azun- for 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
}
1
Trevor

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

    }

}
0
RyanO

David Williames answer をチェックしませんでしたが、誰かが別の方法を探している場合に備えて、ソリューションを投稿します。

Swift 4

まず、カスタムUISliderを作成して、バーのタッチも検出するようにします。

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

(ストーリーボードで、スライダーをこのCustomSliderに設定することを忘れないでください)

スライダーを表示しているビューコントローラーのonviewDidLoad:

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()
}
0
Diogo Souza