What I want to get is a UISlider
which lets the user not only slide when he starts on its thumbRect
, but also when he taps elsewhere. When the user taps on the slider but outside of the thumbRect
, the slider should jump to that value and then still keeping up to the user's sliding gesture.
What I have tried so far was implementing a subclass of UIGestureRecognizer like in this suggestion. It starts right then when a touch down somewhere outside the thumbRect
occurs. The problem is that the slider sets its value but then further sliding gestures are ignored because the touch down recognizer has stolen the touch.
How can I implement a slider where you can tap anywhere but still slide right away?
Edit: ali59a was so kind to add an example of what I've done now. This requires to lift the finger again, after that I can touch and drag to slide (a tap is not what I want, I need a 'touch and slide' right away).
I'm not sure if you are still looking for an answer for this, but I was just looking at this myself today; and I managed to get it to work for me.
The key to it, is using a UILongPressGestureRecognizer
instead of just a UITapGestureRecognizer
, we can then set the minimumPressDuration
of the recognizer to 0; making it act as a tap recognizer, except you can now actually check its state.
Putting what ali59a suggested will work for you, just by replacing the UITapGestureRecognizer
with a UILongPressGestureRecognizer
. However, I found that this didn't seem to quite put the thumbRect directly under my thumb. It appeared a bit off to me.
I created my own UISlider subclass for my project, and here is how I implemented the "tap and slide feature" for me.
In my init
method:
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(tapAndSlide:)];
longPress.minimumPressDuration = 0;
[self addGestureRecognizer:longPress];
Then my tapAndSlide:
method:
- (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];
}
Where I also used a method to return the frame of my custom thumbRect:
- (CGRect)thumbRect {
CGRect trackRect = [self trackRectForBounds:self.bounds];
return [self thumbRectForBounds:self.bounds trackRect:trackRect value:self.value];
}
I also have my slider animate to the position where the user first taps, over 0.35 seconds. Which I reckon looks pretty sweet, so I included that in that code. If you don't want that, simply try this:
- (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];
}
I hope that makes sense, and helps you.