How to detect the end of UISlider drag?
Asked Answered
S

16

128

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

Superphysical answered 22/2, 2012 at 6:46 Comment(1)
Use:[slider addTarget:self action:@selector(sliderStoppedDragging:) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside];Woofer
P
219

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
Preexist answered 22/2, 2012 at 8:8 Comment(16)
This is the best answer to this question. I wish we could changed the answer to this.Campania
i.imgur.com/0Edd2xe.png?1 XCode version 6.x has this feature of setting setContinuous via IDE itself.Familiarity
Yes, best answer is this.Washbasin
Xcode 7.3 Interface Builder has Events [x] Continuous Updates setting for UISliderView. Unchecking this sets continuous to false/NO.Whiskey
Great Answer. Here is the Swift 4 Code: self.mySlider.isContinuous = falseKindly
This is not what the author is asking. He want continuously slide, and also want to know when to stop.Lotty
@lbsweek: OP (author) is asking <quote>how to detect the end of slider drag</quote>. There is no much maneuvering space for free interpretation left in the wording of this question.Preexist
@RokJarc That's fine, this solution meet most developer's need according to the feedback.Lotty
I need both, so it doesn't really answer the question.Ilailaire
@ChewieTheChorkie: it answers OP's question: that is what SO is all about ;-)Preexist
What if you do need data inbetween drag?Zymogen
In that case you leave countinuous property on YES - default state.Preexist
This doesn't answer the question; it only provides a workaround that was useful in this case.Qulllon
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.Dinse
@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.Zymogen
@AlbertRenshaw: OP was asking about detecting the end of slider drag. Probably because he was not interested in values between.Preexist
F
173

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

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.

Flanch answered 9/8, 2016 at 3:18 Comment(15)
Works perfectly! Where did you get the source for that?Buddleia
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.Flanch
Is this a robust way to handle it? What if there are multiple events in allTouches which belong to different phases?Sidras
@Sidras UISlider has multipleTouchEnabled = NO by default.Flanch
Remember to set isContinuous = true in your UISlider.Hoagy
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.Manzoni
@Manzoni 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.Flanch
But my slider is not smooth. I am calling slider like this progressSlider.addTarget(self, action: #selector(sliderTouchEvents), for: UIControlEvents.valueChanged)Manzoni
@Manzoni 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.Flanch
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.Slovenia
@Manzoni 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.Shampoo
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.Deason
Also note that allTouches will be nil when incrementing/decrementing the value with VoiceOver.Deason
Unfortunately, this does not work in Catalyst, where event is uninitialized and hence allTouches is nil. Any workaround?Keefer
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.Zymogen
D
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");
}
Doordie answered 3/3, 2012 at 7:1 Comment(0)
A
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")
    }
    
Assam answered 10/5, 2017 at 15:49 Comment(2)
hello. How can i pause video when start dragging?Ankylosaur
This doesn't detect whether or not a drag has ever happened necessarily, and can be triggered by simply tapping without dragging.Ilailaire
A
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.

Aekerly answered 22/2, 2012 at 7:2 Comment(5)
@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.Depew
The behavior of UISlider has changed since I wrote this answer.Aekerly
Can confirm that you do need to also register a handler for UIControlEventTouchUpOutside in iOS 9.Hideous
I was never able to invoke UIControlEventTouchCancel handler in iOS 9. But I'm able to invoke UIControlEventTouchUpInside and UIControlEventTouchUpOutside only.Seif
The answer taking two parameters, above, does not work in Catalyst. See my comment there. This answer does, though.Keefer
L
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

Luzluzader answered 22/2, 2012 at 6:55 Comment(0)
C
7

I think you need the control event UIControlEventTouchUpInside.

Critter answered 22/2, 2012 at 7:3 Comment(0)
K
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")
    }
}
Kirkwood answered 13/3, 2017 at 17:43 Comment(2)
Good solution, but careful because Slider Released is called when begin dragging and end draggingAssam
and sometimes not called "Slider Released" on releaseAcaulescent
L
4

Connect Touch Up Inside and Value Changed

enter image description here

Luxurious answered 11/9, 2016 at 8:39 Comment(0)
K
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
  }
}
Kylakylah answered 13/5, 2017 at 12:33 Comment(0)
P
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)")
}
Preferable answered 22/12, 2017 at 9:20 Comment(0)
P
3

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

Policlinic answered 15/7, 2020 at 18:10 Comment(0)
D
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.

Dalmatic answered 21/12, 2017 at 8:32 Comment(0)
T
2

RxSwift:

self.slider.rx.controlEvent([.touchUpInside, .touchUpOutside])
    .bind(to: self.viewModel.endSlidingSlider)
    .disposed(by: self.disposeBag)
Transom answered 3/9, 2019 at 12:9 Comment(0)
F
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.

Freya answered 19/3, 2016 at 23:2 Comment(0)
P
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);
}
Postilion answered 22/2, 2012 at 6:58 Comment(1)
That sends the action every time the slider's thumb moves, not just when the drag has ended.Aekerly

© 2022 - 2024 — McMap. All rights reserved.