views:

2373

answers:

9

I have a slider that I'm using to set a float value somewhere. I connect Value Changed to a method in my viewController. That part works fine.

I need to know when the user starts touching the control but not necessarily every single instant that the slider changes (I receive the Value Changed events for that). So I connected a Touch Up Inside event to another method in the viewController.

The problem it, that method gets called twice when the user touches the UISlider control. WTF? It doesn't work that way with UIButtons or other touch events like Touch Down.

I can work around it, I think, but it seems like a bug in the way the slider control handles touches. Does anybody know why it happens?

BTW: the double touch event happens even when Touch Up Inside is the only connected event.

A: 

I just tried, and I notice the same behavior. It get's called from two places, first from UISlider's endTrackingWithTouch:withEvent: method (which is called by UIControl's touchesEnded:withEvent: method), and then from the same UIControl's touchesEnded:withEvent: method (which first called the endTrakingWithTouch:withEvent: method).

Also, when the touch ended outside the slider, the method gets called only once by the endTrackingWithTouch:withEvent: method. The touchesEnded:withEvent: method calls this, but then does not call the callback as it did when the touch ended inside the control.

Anyhow, why do you connect Touch Up Inside? You want to know when the user starts touching the control right? I think you should then connect Touch Down instead of Touch Up Inside.

Edit: I see you need the changed value, and that value changed is called too often. I just re-ran the test, and noticed the following. The value changed is called between the two touch up inside calls. Maybe you can do something with that?

drvdijk
I need to do stuff after the sliding is done. Touch Down fires before the sliding is done and Value Changed fires whenever there is sliding happening, which is too often.
willc2
I changed my original answer :)
drvdijk
A: 

Since my previous comment was not well received, I decided to come up with a simple work around for the problem in case anyone is interested.

I know that it isn't the most efficient solution in the world, but it works just the same.

I created two methods: sliderUp and sliderChanged linked to TouchUp and ValueChanged, respectively. I also created a global int variable (timesFired, if you will) set to initially set to zero

In the sliderUp method I put any code that I didn't mind firing twice. In my case I shoot the slider back down to the bottom if it is not at the top (like the slide to unlock function). I also reset the timesFired back to zero after a delay. to allow for the other method to be called. just in case. (performSelector: withObject: afterDelay: if you don't know how to do that)

In the sliderChanged method I first have an if statement: if(timesFired < 1) { } (I also have an if statement to make sure my slider is at the maximum) and then include all of the code that I want to happen only once, when the slider reaches the maximum value. to make sure that that code only gets fired once, i also increment timesFired.

As soon as the user lifts up their finger on the timesFired will be set back to Zero and the whole process can start anew.

Now, as soon as I can figure out how to freeze the slider after they get to the top, I will actually be happy with this solution. (will simply disabling it work?)

+1  A: 

I had this problem as well, my solution was to add a boolean "allowEvent". Then I connected the UIControlEventTouchDown to a function that set "allowEvent" to YES.

In the selector for the UIControlEventTouchUpInside, i checked if allowEvent is YES, then execute the event and set allowEvent to NO.

Then the event will only be executed once even if it fires twice.

Hope it helps!

Magnus Ahlberg
+1  A: 

I think the simplest solution is to use a flag solution mentioned above.

I've created an flag called (BOOL)pressed and added to TouchUpInside and TouchDown events. So, you just need to check if pressed == YES.

// On down event
pressed = YES;

// On up event
if (pressed)
{
  pressed = NO;
  // code here

}

I hope it helps.

Hami

Hamiseixas
+2  A: 

Thank you all. I have the same issue. Here is some integer math that solves it for me:

    static UInt32 counter = 0;

counter++;

if((counter/2)*2 != counter) return;
Dan Selig
+1  A: 

if you are willing to sub-class the slider, you can easily solve this by implementing endTrackingWithTouch and doing nothing:

- (void) endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{}

the only caveat here is that some specific UIControlEvents will not fire properly. i believe they are the following ones, but i didn't do specific testing to see which ones work and which don't. however touch changed and touch up inside definitely do work without duplicate firing.

   UIControlEventTouchDragInside     = 1 <<  2,
   UIControlEventTouchDragOutside    = 1 <<  3,
   UIControlEventTouchDragEnter      = 1 <<  4,
   UIControlEventTouchDragExit       = 1 <<  5,
jason
A: 

A simple solution, that won't break if Apple fix this ...feature (?) in the future is to add a property that stores the time since last interaction. and exit if it is an impossible short time since last interaction.

(in the code below, latestTimeMark, initially set to current time in viewDidLoad)

NSDate *now = [NSDate date];
 double nowDouble = [now timeIntervalSince1970];
 double difference = nowDouble - self.latestTimeMark;
 self.latestTimeMark = nowDouble;
 //NSLog(@"difference is now: %f", difference);
 if(difference<0.01f) return;
A: 

Just so it doesn't break with future updates

[customSlider addTarget:self action:@selector(sliderDown:) forControlEvents:UIControlEventTouchDown];

[customSlider addTarget:self action:@selector(sliderAction:) forControlEvents:UIControlEventTouchUpInside];



- (void)sliderDown:(id)sender
{

    sliderused = NO;

}

- (void)sliderAction:(id)sender
{
    if(sliderused){
    return;
}

sliderused = YES;

//Your Code Here
}
bentech
A: 

This bug has been fixed as of 4.2. I am now using Dan's code as follows:

static UInt32 counter = 0;

if ([[UIDevice currentDevice].systemVersion compare: @"4.2" options: NSNumericSearch] == NSOrderedAscending) {
    counter++;
    if((counter/2)*2 != counter) return;
}
Ivan Leider