36

I'm creating a custom UISlider to test out some interface ideas. Mostly based around making the thumb image larger.

I found out how to do that, like so:

UIImage *thumb = [UIImage imageNamed:@"newThumbImage_64px.png"];  
[self.slider setThumbImage:thumb forState:UIControlStateNormal];  
[self.slider setThumbImage:thumb forState:UIControlStateHighlighted];  
[thumb release];  

To calculate a related value I need to know where the center point of the thumb image falls when it's being manipulated. And the point should be in it's superview's coordinates.

Looking at the UISlider docs, I didn't see any property that tracked this.

Is there some easy way to calculate this or can it be derived from some existing value(s)?

willc2
  • 36,081
  • 24
  • 84
  • 99

12 Answers12

58

This will return the correct X position of center of thumb image of UISlider in view coordinates:

- (float)xPositionFromSliderValue:(UISlider *)aSlider {
     float sliderRange = aSlider.frame.size.width - aSlider.currentThumbImage.size.width;
     float sliderOrigin = aSlider.frame.origin.x + (aSlider.currentThumbImage.size.width / 2.0);

     float sliderValueToPixels = (((aSlider.value - aSlider.minimumValue)/(aSlider.maximumValue - aSlider.minimumValue)) * sliderRange) + sliderOrigin;

     return sliderValueToPixels;
}

Put it in your view controller and use it like this: (assumes property named slider)

float x = [self xPositionFromSliderValue:self.slider];
JaySH
  • 464
  • 4
  • 12
willc2
  • 36,081
  • 24
  • 84
  • 99
  • 3
    this works for me, except for needing one change to allow for sliders with custom min/max values: float sliderValueToPixels = (aSlider.value/aSlider.maximumValue * sliderRange) + sliderOrigin; Also, it is called like so: float x = [self xPositionFromSliderValue:someSlider]; – James J Mar 24 '10 at 20:41
  • Perfect in combination with James's modification – David Snabel-Caunt Aug 18 '11 at 12:17
  • 9
    Actually in order to accomodate a custom minimum value that is not 0 (or close to it), you'll need to change it to: float sliderValueToPixels = (((aSlider.value-aSlider.minimumValue)/(aSlider.maximumValue-aSlider.minimumValue)) * sliderRange) + sliderOrigin; – Joel Mar 07 '12 at 04:23
  • Thanks Joel! Your additional code helped me because I had a custom min and max value. – jmosesman Jul 18 '12 at 01:27
  • This method broke for me in iOS 7. The answer below that uses thumbRectForBounds::: works perfectly for me in both iOS 6 & 7. – Scott Means Nov 26 '13 at 20:47
  • If UISlider has the default thumb image, then currentThumbImage returns nil. How can you calculate the width in that case? EDIT: I answer myself, the default thumb image is equal to the slider height. – WedgeSparda Aug 26 '15 at 12:48
25

I tried this after reading the above suggestion -

yourLabel = [[UILabel alloc]initWithFrame:....];

//Call this method on Slider value change event

-(void)sliderValueChanged{
    CGRect trackRect = [self.slider trackRectForBounds:self.slider.bounds];
    CGRect thumbRect = [self.slider thumbRectForBounds:self.slider.bounds
                               trackRect:trackRect
                                   value:self.slider.value];

    yourLabel.center = CGPointMake(thumbRect.origin.x + self.slider.frame.origin.x,  self.slider.frame.origin.y - 20);
}

For Swift version

func sliderValueChanged() -> Void {
        let trackRect =  self.slider.trackRect(forBounds: self.slider.bounds)
        let thumbRect = self.slider.thumbRect(forBounds: self.slider.bounds, trackRect: trackRect, value: self.slider.value)
        yourLabel.center = CGPoint(x: thumbRect.origin.x + self.slider.frame.origin.x + 30, y: self.slider.frame.origin.y - 60)
    }

I could get most accurate value by using this snippet.

Zulqarnain
  • 1,417
  • 1
  • 19
  • 25
girish_vr
  • 3,041
  • 1
  • 22
  • 27
22

Swift 3

extension UISlider {
    var thumbCenterX: CGFloat {
        let trackRect = self.trackRect(forBounds: frame)
        let thumbRect = self.thumbRect(forBounds: bounds, trackRect: trackRect, value: value)
        return thumbRect.midX
    }
}
Elijah
  • 7,223
  • 2
  • 49
  • 49
Med Abida
  • 1,124
  • 10
  • 30
11

I would like to know why none of you provide the simplest answer which consist in reading the manual. You can compute all these values accurately and also MAKING SURE THEY STAY THAT WAY, by simply using the methods:

- (CGRect)trackRectForBounds:(CGRect)bounds
- (CGRect)thumbRectForBounds:(CGRect)bounds trackRect:(CGRect)rect value:(float)value

which you can easily find in the developer documentation.

If thumb image changes and you want to change how it's positioned, you subclass and override these methods. The first one gives you the rectangle in which the thumb can move the second one the position of the thumb itself.

Mohamed Elkassas
  • 649
  • 1
  • 11
  • 28
Psycho
  • 1,863
  • 1
  • 16
  • 17
  • 1
    This is definetive THE answer! Just update a member variable containing the return value of [super trackRectForBounds] and you can skip all the calculations. Excellent! – Krumelur Mar 18 '12 at 09:36
  • 7
    Documentation says for both of these methods: "You should not call this method directly." – Igor Kulagin Mar 21 '13 at 17:39
10

It's better to use -[UIView convertRect:fromView:] method instead. It's cleaner and easier without any complicated calculations:

- (IBAction)scrub:(UISlider *)sender
{
    CGRect _thumbRect = [sender thumbRectForBounds:sender.bounds
                                        trackRect:[sender trackRectForBounds:sender.bounds]
                                            value:sender.value];
    CGRect thumbRect = [self.view convertRect:_thumbRect fromView:sender];

    // Use the rect to display a popover (pre iOS 8 code)
    [self.popover dismissPopoverAnimated:NO];
    self.popover = [[UIPopoverController alloc] initWithContentViewController:[UIViewController new]];
    [self.popover presentPopoverFromRect:thumbRect inView:self.view
                permittedArrowDirections:UIPopoverArrowDirectionDown|UIPopoverArrowDirectionUp animated:YES];
}
Yariv Nissim
  • 12,599
  • 1
  • 36
  • 44
7

I approached it by first mapping the UISlider's value interval in percents and then taking the same percent of the slider's size minus the percent of the thumb's size, a value to which I added half of the thumb's size to obtain its center.

    - (float)mapValueInIntervalInPercents: (float)value min: (float)minimum max: (float)maximum
    {
        return (100 / (maximum - minimum)) * value -
               (100 * minimum)/(maximum - minimum);
    }    


    - (float)xPositionFromSliderValue:(UISlider *)aSlider
    {
        float percent = [self mapValueInIntervalInPercents: aSlider.value
                              min: aSlider.minimumValue
                              max: aSlider.maximumValue] / 100.0;

        return percent * aSlider.frame.size.width -
               percent * aSlider.currentThumbImage.size.width +
               aSlider.currentThumbImage.size.width / 2;
    }
luvieere
  • 35,580
  • 18
  • 120
  • 178
3

Swift 3.0
Please refer if you like.

import UIKit

extension UISlider {

    var trackBounds: CGRect {
        return trackRect(forBounds: bounds)
    }

    var trackFrame: CGRect {
        guard let superView = superview else { return CGRect.zero }
        return self.convert(trackBounds, to: superView)
    }

    var thumbBounds: CGRect {
        return thumbRect(forBounds: frame, trackRect: trackBounds, value: value)
    }

    var thumbFrame: CGRect {
        return thumbRect(forBounds: bounds, trackRect: trackFrame, value: value)
    }
}
2

AFter a little playing with IB and a 1px wide thumb image, the position of the thumb is exactly where you'd expect it:

UIImage       *thumb = [UIImage imageNamed:@"newThumbImage_64px.png"];  
CGRect        sliderFrame = self.slider.frame;
CGFloat       x = sliderFrame.origin.x + slideFrame.size.width * slider.value + thumb.size.width / 2;
CGFloat       y = sliderFrame.origin.y + sliderFrame.size.height / 2;

return CGPointMake(x, y);
Ben Gottlieb
  • 84,498
  • 22
  • 171
  • 170
  • Assuming slider range 0..1, your point is correct when the slider.value is 0, but gets more off as slider.value changes. Also, there's a typo in the x equation. – willc2 Nov 25 '09 at 16:05
  • Also, shouldn't sliderFrame.x be sliderFrame.origin.x & sliderFrame.size.width? – willc2 Nov 25 '09 at 16:09
  • Oops, sorry about the typo, fixed. – Ben Gottlieb Nov 25 '09 at 18:12
  • Still not getting the right value. When testing, I'm setting a transparent view's center point using your computed x,y point. – willc2 Nov 25 '09 at 19:21
  • Upon further testing, it appears there's a 1-4px adjustment, depending on where on the slider you are. I've posted my test code here: http://dl.dropbox.com/u/85235/ThumbTest.zip. Using it and xScope, you can play around and find the exact values. – Ben Gottlieb Nov 26 '09 at 05:16
1

Here is a Swift 2.2 solution, I created an extension for it. I have only tried this with the default image.

import UIKit

extension UISlider {

    var thumbImageCenterX: CGFloat {
        let trackRect = trackRectForBounds(bounds)
        let thumbRect = thumbRectForBounds(bounds, trackRect: trackRect, value: value)

        return thumbRect.origin.x + thumbRect.width / 2 - frame.size.width / 2
    }
}
Ovi Bortas
  • 3,382
  • 1
  • 11
  • 10
0

Above solution is useful when UISlider is horizontal. In a recent project,we need to use UISlider with angle. So I need to get both x and y position. Using below to calculate the x,y axis:

- (CGPoint)xyPositionFromSliderValue:(UISlider *)aSlider WithAngle:(double)aangle{
   //aangle means the dextrorotation angle compare to horizontal.
   float xOrigin = 0.0;
   float yOrigin = 0.0;
   float xValueToaXis=0.0;
   float yValueToaXis=0.0;
   float sliderRange = slider_width-aSlider.currentThumbImage.size.width;
   xOrigin = aSlider.frame.origin.x+slider_width*fabs(cos(aangle/180.0*M_PI));
   yOrigin = aSlider.frame.origin.y;

   xValueToaXis = xOrigin + ((((((aSlider.value-aSlider.minimumValue)/(aSlider.maximumValue-aSlider.minimumValue)) * sliderRange))+(aSlider.currentThumbImage.size.width / 2.0))*cos(aangle/180.0*M_PI)) ;
   yValueToaXis =  yOrigin + ((((((aSlider.value-aSlider.minimumValue)/(aSlider.maximumValue-aSlider.minimumValue)) * sliderRange))+(aSlider.currentThumbImage.size.width / 2.0))*sin(aangle/180.0*M_PI));

   CGPoint xyPoint=CGPointMake(xValueToaXis, yValueToaXis);
   return xyPoint;
}

Besides, can I Create a Ranger Slider based on UISlider? Thanks.

Ding
  • 51
  • 4
0

This will work for the UISlider being placed anywhere on the screen. Most of the other solutions will only work when the UISlider is aligned with the left edge of the screen. Note, I used frame rather than bounds for the thumbRect, to achieve that. And I show two variations, based on using frame or bounds for the trackRect

extension UISlider {

    //this version will return the x coordinate in relation to the UISlider frame
    var thumbCenterX: CGFloat {
        return thumbRect(forBounds: frame, trackRect: trackRect(forBounds: bounds), value: value).midX
    }

    //this version will return the x coordinate in relation to the UISlider's containing view
    var thumbCenterX: CGFloat {
        return thumbRect(forBounds: frame, trackRect: trackRect(forBounds: frame), value: value).midX
    }

}
Elijah
  • 7,223
  • 2
  • 49
  • 49
0

step 1 :get View for detect position (use same extension top commet of# Ovi Bortas)

@IBOutlet weak var sliderView: UIView!

step 2 : set label frame for add sub view func setLabelThumb(slider:UISlider,value:Float){ slider.value = value

let label = UILabel(frame:  CGRect(x: slider.thumbCenterX - 20, y: slider.frame.origin.y - 25, width: 50, height: 30))
label.font = UIFont.systemFont(ofSize: 10.0)
label.textColor = UIColor.red
label.textAlignment = .center
label.text = "\(value) kg."

sliderView.addSubview(label)

}

Papon Smc
  • 356
  • 3
  • 6