125

How to detect the event when the user has ended the drag of a slider pointer?

Alex Cio
  • 6,014
  • 5
  • 44
  • 74
DroidHeaven
  • 2,414
  • 3
  • 25
  • 31

16 Answers16

216

If you don't need any data inbetween drag, than you should simply set:

[mySlider setContinuous: NO];

This way you will receive valueChanged event only when the user stops moving the slider.

Swift 5 version:

mySlider.isContinuous = false
Nikita
  • 460
  • 5
  • 11
Rok Jarc
  • 18,765
  • 9
  • 69
  • 124
  • 3
    This is the best answer to this question. I wish we could changed the answer to this. – M. Porooshani Sep 10 '15 at 04:34
  • 1
    http://i.imgur.com/0Edd2xe.png?1 XCode version 6.x has this feature of setting setContinuous via IDE itself. – Abhijeet Sep 14 '15 at 11:34
  • 1
    Yes, best answer is this. – sabiland Jan 17 '16 at 12:36
  • 1
    Xcode 7.3 Interface Builder has `Events [x] Continuous Updates` setting for UISliderView. Unchecking this sets `continuous` to false/NO. – leanne Jul 13 '16 at 22:16
  • 3
    Great Answer. Here is the Swift 4 Code: `self.mySlider.isContinuous = false` – Marco Weber Dec 16 '17 at 11:52
  • 5
    This is not what the author is asking. He want continuously slide, and also want to know when to stop. – andrewchan2022 Sep 05 '18 at 06:29
  • 2
    @lbsweek: OP (author) is asking how to detect **the end** of slider drag. There is no much maneuvering space for free interpretation left in the wording of this question. – Rok Jarc Sep 05 '18 at 07:14
  • @RokJarc That's fine, this solution meet most developer's need according to the feedback. – andrewchan2022 Sep 05 '18 at 07:31
  • 2
    I need both, so it doesn't really answer the question. – Chewie The Chorkie Nov 06 '18 at 16:21
  • @ChewieTheChorkie: it answers OP's question: that is what SO is all about ;-) – Rok Jarc Nov 07 '18 at 14:43
  • 2
    What if you do need data inbetween drag? – Albert Renshaw Dec 14 '18 at 02:39
  • In that case you leave countinuous property on YES - default state. – Rok Jarc Dec 14 '18 at 12:26
  • 2
    This doesn't answer the question; it only provides a workaround that was useful in this case. – Oscar Nov 17 '19 at 22:36
  • 1
    Thankssss, you save my Fxxxing time, man! I'm struggling about which event to detect the end of drag for slider, and didn't find one. And I don't care about values during the slider change the audio track currentTime. – Zhou Haibo Jun 03 '21 at 11:21
  • @M.Porooshani Disagree; this answer makes it so you can't read values mid-drag which is a core functionality of UISlider. An ideal answer (which exists in this thread thankfully) is one that allows you to use a UISlider as expected while also being able to detect the UITouchPhaseEnded event. – Albert Renshaw Dec 28 '22 at 01:23
  • @AlbertRenshaw: OP was asking about detecting the end of slider drag. Probably because he was not interested in values between. – Rok Jarc Jan 03 '23 at 17:17
169

You can add an action that takes two parameters, sender and an event, for UIControlEventValueChanged:

[slider addTarget:self action:@selector(onSliderValChanged:forEvent:) forControlEvents:UIControlEventValueChanged]

Then check the phase of the touch object in your handler:

- (void)onSliderValChanged:(UISlider*)slider forEvent:(UIEvent*)event {     
    UITouch *touchEvent = [[event allTouches] anyObject];
    switch (touchEvent.phase) {     
        case UITouchPhaseBegan:
            // handle drag began
            break;
        case UITouchPhaseMoved:
            // handle drag moved
            break;
        case UITouchPhaseEnded:
            // handle drag ended
            break;
        default:
            break;
    }
}

Swift 4 & 5

slider.addTarget(self, action: #selector(onSliderValChanged(slider:event:)), for: .valueChanged)
@objc func onSliderValChanged(slider: UISlider, event: UIEvent) {
    if let touchEvent = event.allTouches?.first {
        switch touchEvent.phase {
        case .began:
            // handle drag began
        case .moved:
            // handle drag moved
        case .ended:
            // handle drag ended
        default:
            break
        }
    }
}

Note in Interface Builder when adding an action you also have the option to add both sender and event parameters to the action.

Devin Pitcher
  • 2,562
  • 1
  • 18
  • 11
  • 2
    Works perfectly! Where did you get the source for that? – Roi Mulia Aug 29 '16 at 07:06
  • 3
    thanks @RoiMulia, I don't remember where I first saw it, I've been using in sliders for years. I sometimes use it to ignore the change on UITouchPhaseEnded since the value tends to jump when the user lifts their finger. – Devin Pitcher Aug 30 '16 at 03:49
  • Is this a robust way to handle it? What if there are multiple events in `allTouches` which belong to different phases? – David Mar 09 '17 at 23:03
  • @David UISlider has multipleTouchEnabled = NO by default. – Devin Pitcher Mar 10 '17 at 04:48
  • 2
    Remember to set `isContinuous = true` in your `UISlider`. – krlbsk Sep 20 '17 at 07:24
  • I wrote some code in case : .move and .end, now what happen is slider thumb Image is not moving smoothly.With out any stuff in .move and , .end slider is moving smoothly. – Malleswari Jan 04 '18 at 12:36
  • @Malleswari as you move the slider the .moved case will gets called continuously (i.e. many many times) on the main thread as the slider position updates. So you don't want to do any processor or I/O intensive work in the .move case. – Devin Pitcher Jan 05 '18 at 00:37
  • But my slider is not smooth. I am calling slider like this progressSlider.addTarget(self, action: #selector(sliderTouchEvents), for: UIControlEvents.valueChanged) – Malleswari Jan 05 '18 at 06:48
  • @Malleswari what are you doing inside the .move case? You shouldn't do anything that affects the state of the slider or that takes more than a few milliseconds to process. – Devin Pitcher Jan 05 '18 at 16:00
  • 4
    This is a great solution, however it has an issue with touch cancelling. Despite there being a "cancelled" event in the touchEvent enum, it is not fired when the touch is cancelled. Therefore I recommend also adding a listener for the touchCancel event from the Slider. This should cover all possibilities. – bnussey Jul 19 '18 at 19:30
  • @Malleswari You can declare this: private var renderTimer = Timer() And then use where you need it as: self.renderTimer = Timer( timeInterval: 0.1, target: self, selector: #selector(AudioMessageCell.renderTimerCycle), userInfo: nil, repeats: true ) Where you can actually change timeInterval, lower it to 0,01 for example, to get your slider moving smoother. – timetraveler90 Jun 18 '19 at 12:23
  • 3
    I saw that sometimes the phase can go: began, stationary, moved, but then never ended nor cancelled. This is problematic for my use case as I expected every interaction to end or be canceled. The fix is noted above: use a separate touch cancel target/action. – Jordan H Jan 28 '20 at 04:00
  • 2
    Also note that `allTouches` will be `nil` when incrementing/decrementing the value with VoiceOver. – Jordan H Jan 28 '20 at 04:49
  • Unfortunately, this does not work in Catalyst, where event is uninitialized and hence allTouches is nil. Any workaround? – Victor Engel Nov 27 '21 at 19:49
  • Probably worth also considering `UITouchPhaseCancelled`. I haven't tested, but if it's anything like GestureRecognizers then the following flow would fail: Drag a slider then simultaneously tap a notification dropdown which exits the app mid-drag thus ending the drag without `UITouchPhaseEnded` being called. – Albert Renshaw Dec 28 '22 at 01:27
74

I use the "Touch Up Inside" and "Touch up outside" notifications.

Interface Builder:

Connect both notifications in the Interface Builder to your receiving method. The method could look like this:

- (IBAction)lengthSliderDidEndSliding:(id)sender {
    NSLog(@"Slider did end sliding... Do your stuff here");
}

In code:

If you want to wire it programatically you would have something like this in your viewWillAppear (or wherever it fits you) call:

[_mySlider addTarget:self
              action:@selector(sliderDidEndSliding:) 
    forControlEvents:(UIControlEventTouchUpInside | UIControlEventTouchUpOutside)];

The receiving method would look like this:

- (void)sliderDidEndSliding:(NSNotification *)notification {
     NSLog(@"Slider did end sliding... Do your stuff here");
}
thomas
  • 1,446
  • 8
  • 20
21

Swift 5 and Swift 4

  1. Add target for touchUpInside and touchUpOutside events. You can do it either programatically or from storyboard. This is how you do it programatically

    slider.addTarget(self, action: #selector(sliderDidEndSliding), for: [.touchUpInside, .touchUpOutside])
    
  2. Write your function to handle the end of slider drag

    func sliderDidEndSliding() {
        print("end sliding")
    }
    
Guy Daher
  • 5,526
  • 5
  • 42
  • 67
20

Since UISlider is a subclass of UIControl, you can set a target and action for its UIControlEventTouchUpInside.

If you want to do it in code, it looks like this:

[self.slider addTarget:self action:@selector(dragEndedForSlider:)
    forControlEvents:UIControlEventTouchUpInside];

That will send you a dragEndedForSlider: message when the touch ends.

If you want to do it in your nib, you can control-click your slider to get its connections menu, and then drag from the “Touch Up Inside” socket to the target object.

You should also add a target and action for UIControlEventTouchUpOutside and for UIControlEventTouchCancel.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • 1
    @rob mayoff Sorry but you do need to use UIControlEventTouchUpOutside. Otherwise the selector won't be called if your finger is not on slider when you finish dragging. – user3164248 Apr 11 '15 at 07:15
  • 2
    The behavior of `UISlider` has changed since I wrote this answer. – rob mayoff Apr 11 '15 at 15:29
  • Can confirm that you *do* need to also register a handler for UIControlEventTouchUpOutside in iOS 9. – lmirosevic Jun 09 '16 at 14:40
  • I was never able to invoke `UIControlEventTouchCancel` handler in iOS 9. But I'm able to invoke `UIControlEventTouchUpInside` and `UIControlEventTouchUpOutside` only. – petrsyn Sep 29 '16 at 13:36
  • The answer taking two parameters, above, does not work in Catalyst. See my comment there. This answer does, though. – Victor Engel Nov 27 '21 at 19:58
9

You can use:

- (void)addTarget:(id)target action:(SEL)action 
                   forControlEvents:(UIControlEvents)controlEvents  

to detect when the touchDown and touchUp events occur in UISlider

Mundi
  • 79,884
  • 17
  • 117
  • 140
Mudit Bajpai
  • 3,010
  • 19
  • 34
7

I think you need the control event UIControlEventTouchUpInside.

Pang
  • 9,564
  • 146
  • 81
  • 122
scorpiozj
  • 2,687
  • 5
  • 34
  • 60
6

Swift 3 answer

I had same problem and eventually got this to work, so maybe it will help somebody. Connect your_Slider to 'Value Changed' in storyboard and when slider moved "Slider Touched" actioned and when let go "Slider Released".

@IBAction func your_Slider(_ sender: UISlider) {
    if (yourSlider.isTracking) {
        print("Slider Touched")
    } else {
        print("Slider Released")
    }
}
Andy
  • 107
  • 1
  • 8
4

Connect Touch Up Inside and Value Changed

enter image description here

Atef
  • 2,872
  • 1
  • 36
  • 32
4

Swift 3.1

Sometimes you will want the slider's drag events to fire continuously, but have the option of executing different code depending on whether the slider is still being dragged or has just finished being dragged.

To do this, I've expanded on Andy's answer above that makes use of the slider's isTracking property. I needed to record two states: sliderHasFinishedTracking and sliderLastStoredValue.

My code correctly:

  • doesn't fire an action upon starting drag

  • fires the action if the user only nudges the slider with a single tap (meaning that isTracking remains false)


class SliderExample: UIViewController {
  var slider: UISlider = UISlider()
  var sliderHasFinishedTracking: Bool = true
  var sliderLastStoredValue: Float!

  override func loadView() {
    super.loadView()

    slider.minimumValue = 100.0
    slider.maximumValue = 200.0
    slider.isContinuous = true
    slider.value = 100.0
    self.sliderLastStoredValue = slider.value
    slider.addTarget(self, action: #selector(respondToSlideEvents), for: .valueChanged)
    // add your slider to the view however you like
  }

  func respondToSlideEvents(sender: UISlider) {
    let currentValue: Float = Float(sender.value)
    print("Event fired. Current value for slider: \(currentValue)%.")

    if(slider.isTracking) {
      self.sliderHasFinishedTracking = false
      print("Action while the slider is being dragged ⏳")
    } else {
      if(self.sliderHasFinishedTracking && currentValue == self.sliderLastStoredValue){
        print("Slider has finished tracking and its value hasn't changed, " +
              "yet event was fired anyway. ") // No need to take action.
      } else {
        self.sliderHasFinishedTracking = true
        print("Action upon slider drag/tap finishing ⌛️")
      }
    }

    self.sliderLastStoredValue = currentValue
  }
}
Jamie Birch
  • 5,839
  • 1
  • 46
  • 60
4

In Swift 4 you can use this function

1 Frist step:- Adding the target in slider

self.distanceSlider.addTarget(self, action: #selector(self.sliderDidEndSliding(notification:)), for: ([.touchUpInside,.touchUpOutside]))

2 Second step:- Create a function

@objc func sliderDidEndSliding(notification: NSNotification)
{
   print("Hello-->\(distanceSlider.value)")
}
mike_t
  • 2,484
  • 2
  • 21
  • 39
pansora abhay
  • 892
  • 10
  • 16
2

Maybe you should add target for UIControlEventTouchUpInside、UIControlEventTouchUpOutside、UIControlEventTouchCancel events.

I met the same problem before , because i don't add target for UIControlEventTouchCancel event.

zhuoming
  • 17
  • 1
2

RxSwift:

self.slider.rx.controlEvent([.touchUpInside, .touchUpOutside])
    .bind(to: self.viewModel.endSlidingSlider)
    .disposed(by: self.disposeBag)
Adam Smaka
  • 5,977
  • 3
  • 50
  • 55
2

Create an action and outlet for the slider (or you can typecast sender from action to UISlider), and in the UI Uncheck the Continuous Updates check box. This way we will get the slider value only when slider stops dragging.

@IBAction func actionSlider(_ sender: Any) {
   let value = sliderSpeed.value.rounded()
}

enter image description here

1

I had similar issue recently. Here is what I did in Swift:

    theSlider.continuous = false;

The code that holds this continuous value reads:

public var continuous: Bool // if set, value change events are generated
    // any time the value changes due to dragging. default = YES

The default seems to be YES (true). Setting it it to false does the thing you want.

Evdzhan Mustafa
  • 3,645
  • 1
  • 24
  • 40
0

Try like this

customSlider.minimumValue = 0.0;
    customSlider.maximumValue = 10.0;
[customSlider addTarget:self action:@selector(changeSlider:) forControlEvents:UIControlEventValueChanged];


-(void)changeSlider:(id)sender{
    UISlider *slider=(UISlider *)sender;
    float sliderValue=(float)(slider.value );
NSLog(@"sliderValue = %f",sliderValue);
}
Raj Subbiah
  • 1,292
  • 11
  • 20