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
You should add a tap gesture on your UISlider
.
Exemple :
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sliderTapped:)];
[_slider addGestureRecognizer:tapGestureRecognizer];
In sliderTapped you can get the location and update the value of the slider :
- (void)sliderTapped:(UIGestureRecognizer *)gestureRecognizer {
CGPoint pointTaped = [gestureRecognizer locationInView:gestureRecognizer.view];
CGPoint positionOfSlider = _slider.frame.origin;
float widthOfSlider = _slider.frame.size.width;
float newValue = ((pointTaped.x - positionOfSlider.x) * _slider.maximumValue) / widthOfSlider;
[_slider setValue:newValue];
}
I create an example here : https://github.com/ali59a/tap-and-slide-in-a-UISlider
Updated tsji10dra's answer to Swift 4:
@IBAction func sliderTappedAction(sender: UITapGestureRecognizer) {
if let slider = sender.view as? UISlider {
if slider.isHighlighted { return }
let point = sender.location(in: slider)
let percentage = Float(point.x / slider.bounds.size.width)
let delta = percentage * (slider.maximumValue - slider.minimumValue)
let value = slider.minimumValue + delta
slider.setValue(value, animated: true)
// also remember to call valueChanged if there's any
// custom behaviour going on there and pass the slider
// variable as the parameter, as indicated below
self.sliderValueChanged(slider)
}
}
My solution is quite simple:
class CustomSlider: UISlider {
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let newValue = <calculated_value>
self.setValue(newValue, animated: false)
super.sendActions(for: UIControlEvents.valueChanged)
return true
}}
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.
Here is my solution that works :
import UIKit
class CustomSlider: UISlider {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
addTapGesture()
}
private func addTapGesture() {
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
addGestureRecognizer(tap)
}
@objc private func handleTap(_ sender: UITapGestureRecognizer) {
let location = sender.location(in: self)
let percent = minimumValue + Float(location.x / bounds.width) * maximumValue
setValue(percent, animated: true)
sendActions(for: .valueChanged)
}
}
Adding swift version of Ali AB.'s answer,
@IBAction func sliderTappedAction(sender: UITapGestureRecognizer)
{
if let slider = sender.view as? UISlider {
if slider.highlighted { return }
let point = sender.locationInView(slider)
let percentage = Float(point.x / CGRectGetWidth(slider.bounds))
let delta = percentage * (slider.maximumValue - slider.minimumValue)
let value = slider.minimumValue + delta
slider.setValue(value, animated: true)
}
}