yonat / MultiSlider

UISlider clone with multiple thumbs and values, range highlight, optional snap intervals, optional value labels, either vertical or horizontal.
MIT License
494 stars 113 forks source link

Add .touchDown event #82

Closed lsamaria closed 2 years ago

lsamaria commented 2 years ago

I needed to specifically listen for the .touchDown event and when I added the below code it didn't work.

lazy var slider: MultiSlider = {
    let s = MultiSlider()
    // ...
    s.addTarget(self, action: #selector(sliderBeganTracking(_:)), for: .touchDown)
    return s
}()

@objc func sliderBeganTracking(_ slider: MultiSlider) {
    print("Finger initial tap: \(slider.draggedThumbIndex)")
    // do something **only** on initial touch
}

To get it to work, I had to go into your MultiSlider+Drag file and add the sendActions(for: .touchDown) event to your .began gesture

@objc open func didDrag(_ panGesture: UIPanGestureRecognizer) {
        switch panGesture.state {
        case .began:
            // ...
            sendActions(for: .touchDown)

Can you please add the sendActions(for: .touchDown) event?

yonat commented 2 years ago

Okay. Out of curiosity, what will your code do on touch down? What are you trying to achieve?

lsamaria commented 2 years ago

I'm using an AVPlayer and a periodicTimeObserver. The issue is when the slider isn't touched and the timeObserver is observing the player, as the player plays the slider moves along. Think of the slider at the bottom of a youtube video. As the video plays, the timeObserver updates the slider, and it follows along.

What I have found is once you tap the slider and decide to scrub to wherever you want the video to play at, there is an issue. If your scrubbing while the timeObserver is observing, there is a fight between where you scrub to and where the timeObserver updates the slider at. It's really sloppy because the slider is popping from place to place as you scrub because of the timeObserver update.

So what I do is once that initial .touchDown is detected, I immediately pause the player and check to see if the player was playing before the .touchDown event is detected. If it was playing, I set a Bool property "playerWasPlayingBeforeSliderTapped = true". That is only set on .touchDown (if it was playing) and on .touchUpInside, when the finger is lifted I reset it to false. That way once the user is scrubbing, because the player is paused, the timeObserver is no longer observing (no fight). And once the use lifts their finger, if the player was playing, I can resume playing.

It might sound complicated but it's really smooth. You can scrub with 0 problems and once you let go it plays from the point you lifted your finger.

I can post an example if you want. Maybe you'll need to use it one day. Let me know, I got you.

Btw so far your library is good. Outside of the 2nd right thumb issue you did a GREAT JOB!!!

THANK YOU!!!

yonat commented 2 years ago

Can you use the regular .valueChanged event? It's not called when you change the value programmatically.

lsamaria commented 2 years ago

If you put a print statement on the .valueChanged, you'll see that once it's touched, for some reason it fires twice. I even added a Bool that I switch from true to false and back to true when the finger is lifted but that didn't work. I forgot why

yonat commented 2 years ago

I only see it firing once dragged. Maybe you're doing something different?

Please create a minimal project that reproduces this problem, create a github project with its code, and link to it here. (See https://ootips.org/yonat/repex/)

lsamaria commented 2 years ago

I'll try it on a fresh project and see what happens. If it doesn't work I'll send the code.

Thanks

yonat commented 2 years ago

@lsamaria Did you resolve the problem?