Is that possible to add a label to the UISlider's thumb image?
Asked Answered
M

8

15

I want to add a label to the slider's thumb which should show the value of the slider and changes too when thumbs is being dragged towards right side. Is it possible??

Any comments or suggestion would be appreciated.

Moorhead answered 18/5, 2011 at 15:56 Comment(0)
H
13

You could do something similar to this example which draws text directly to your thumb image. It's a rough example so you will need to change it to make sense for your project.

- (IBAction)sliderValueChanged:(id)sender {
    UISlider *aSlider = (UISlider *)sender;
    NSString *strForThumbImage = 
     [NSString stringWithFormat:@"%.0f", aSlider.value * 100]
    UIImage *thumbImage = [self addText:self.thumbImage 
                                   text:strForThumbImage];
    [aSlider setThumbImage:thumbImage forState:aSlider.state];
}

//Add text to UIImage 
-(UIImage *)addText:(UIImage *)img text:(NSString *)text1{ 
    int w = img.size.width; 
    int h = img.size.height; 
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 
    CGContextRef context = CGBitmapContextCreate( NULL, 
                                                  w, 
                                                  h, 
                                                  8, 
                                                  4 * w, 
                                                  colorSpace, 
                                                  kCGImageAlphaPremultipliedFirst); 
    CGContextDrawImage(context, CGRectMake(0, 0, w, h), img.CGImage); 

    char* text= (char *)[text1 cStringUsingEncoding:NSASCIIStringEncoding]; 
    CGContextSelectFont(context, "Arial",12, kCGEncodingMacRoman); 
    CGContextSetTextDrawingMode(context, kCGTextFill); 
    CGContextSetRGBFillColor(context, 0, 0, 0, 1); 
    CGContextShowTextAtPoint(context,3,8,text, strlen(text)); 
    CGImageRef imgCombined = CGBitmapContextCreateImage(context); 

    CGContextRelease(context); 
    CGColorSpaceRelease(colorSpace); 

    UIImage *retImage = [UIImage imageWithCGImage:imgCombined]; 
    CGImageRelease(imgCombined); 

    return retImage; 
}
Hylton answered 18/5, 2011 at 16:41 Comment(8)
It give me an error saying, request for member thumbImage in something not a structure or union.Moorhead
I fixed the error but still its showing the text below the slider and is not aligned too. Could you give some more hintsMoorhead
Change the CGContextShowTextAtPoint() function to adjust where the text is drawn.Hylton
I got it to change the color of the text you got to use CGContextSetRGBFillColor(context,255,255,255, 1);Moorhead
I think this design may be a better way to go. cocoacontrols.com/platforms/ios/controls/elcsliderHylton
How i can adapt this also for retina display?Cavorelievo
In my case, the image always disappears when UISlider will be dragged. The UIImage is set and it works, but only on drag.Accordance
is there a swift version to this answer?Lafferty
G
16

I grab the thumb image from the slider (UIImageView) and add my label to it. Nice and clean.

UIImageView *handleView = [slider.subviews lastObject];
UILabel *label = [[UILabel alloc] initWithFrame:handleView.bounds];
label.backgroundColor = [UIColor clearColor];
label.textAlignment = NSTextAlignmentCenter;
[handleView addSubview:label];
self.sliderLabel = label;

Then you change the label.text whenever you need to.

Note: the subview order of UISlider could change in the future, however it's unlikely that the thumb would no longer be the topmost view, as it will always be the main point of interaction in a slider.

Swift 3 -- More detailed example (link your slider in IB)

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var slider: UISlider!
    var sliderLabel: UILabel?

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        if let handleView = slider.subviews.last as? UIImageView {
            let label = UILabel(frame: handleView.bounds)
            label.backgroundColor = .clear
            label.textAlignment = .center
            handleView.addSubview(label)

            self.sliderLabel = label
            //set label font, size, color, etc.
            label.text = "!!"
        }
    }
}
Greatuncle answered 1/1, 2014 at 17:31 Comment(9)
How to do this in swift?Joaquinajoash
Two Questions. 1. Where are you putting this code, ViewDidLoad or in the sliderLabel Function? 2. What is the SliderLabel?Venality
@bradfordgray You have to have a reference to your slider: 'slider', and your own property that you will use to later reference the label you are creating and adding to the slider: 'sliderLabel'. You can do this in viewDidLoad, but both of those properties should be defined at the top of your viewController (or whatever file you're in).Greatuncle
So for clarities sake @IBOutlet weak var slider: UISlider!, and make sliderLabel a double. But if you do that, you can't assign a double to a label in that order. I'm slightly confused as to how you are doing this. Thank's for your help.Venality
lastly I don't quite understand, but when I do a breakpoint, slider.subview.last as? UIImageView it is never true.Venality
See expanded example above. Note that the slider element doesn't build it's subviews until after viewDidLoad (I just tested it), so you need to put it in viewDidAppear.Greatuncle
I would actually recommend putting this inside viewDidLayoutSubviews rather than viewDidAppear.Countrybred
This is assuming the slider thumb view will always be the last view, which may not be the case on future iOS versionsSolarize
This is a best solution because you don't need to update label position on slider draw or etc.Karly
H
13

You could do something similar to this example which draws text directly to your thumb image. It's a rough example so you will need to change it to make sense for your project.

- (IBAction)sliderValueChanged:(id)sender {
    UISlider *aSlider = (UISlider *)sender;
    NSString *strForThumbImage = 
     [NSString stringWithFormat:@"%.0f", aSlider.value * 100]
    UIImage *thumbImage = [self addText:self.thumbImage 
                                   text:strForThumbImage];
    [aSlider setThumbImage:thumbImage forState:aSlider.state];
}

//Add text to UIImage 
-(UIImage *)addText:(UIImage *)img text:(NSString *)text1{ 
    int w = img.size.width; 
    int h = img.size.height; 
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 
    CGContextRef context = CGBitmapContextCreate( NULL, 
                                                  w, 
                                                  h, 
                                                  8, 
                                                  4 * w, 
                                                  colorSpace, 
                                                  kCGImageAlphaPremultipliedFirst); 
    CGContextDrawImage(context, CGRectMake(0, 0, w, h), img.CGImage); 

    char* text= (char *)[text1 cStringUsingEncoding:NSASCIIStringEncoding]; 
    CGContextSelectFont(context, "Arial",12, kCGEncodingMacRoman); 
    CGContextSetTextDrawingMode(context, kCGTextFill); 
    CGContextSetRGBFillColor(context, 0, 0, 0, 1); 
    CGContextShowTextAtPoint(context,3,8,text, strlen(text)); 
    CGImageRef imgCombined = CGBitmapContextCreateImage(context); 

    CGContextRelease(context); 
    CGColorSpaceRelease(colorSpace); 

    UIImage *retImage = [UIImage imageWithCGImage:imgCombined]; 
    CGImageRelease(imgCombined); 

    return retImage; 
}
Hylton answered 18/5, 2011 at 16:41 Comment(8)
It give me an error saying, request for member thumbImage in something not a structure or union.Moorhead
I fixed the error but still its showing the text below the slider and is not aligned too. Could you give some more hintsMoorhead
Change the CGContextShowTextAtPoint() function to adjust where the text is drawn.Hylton
I got it to change the color of the text you got to use CGContextSetRGBFillColor(context,255,255,255, 1);Moorhead
I think this design may be a better way to go. cocoacontrols.com/platforms/ios/controls/elcsliderHylton
How i can adapt this also for retina display?Cavorelievo
In my case, the image always disappears when UISlider will be dragged. The UIImage is set and it works, but only on drag.Accordance
is there a swift version to this answer?Lafferty
N
13

A simple implementation of a custom class in Swift 3.2. This is working nicely for me.

class ThumbTextSlider: UISlider {
var thumbTextLabel: UILabel = UILabel()

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

override func layoutSubviews() {
    super.layoutSubviews()

    thumbTextLabel.frame = thumbFrame
    thumbTextLabel.text = Double(value).roundTo(places: 1).description
}

override func awakeFromNib() {
    super.awakeFromNib()
    addSubview(thumbTextLabel)
    thumbTextLabel.textAlignment = .center
    thumbTextLabel.layer.zPosition = layer.zPosition + 1
}
}

I hope it helps :)

Nakasuji answered 1/8, 2017 at 9:26 Comment(2)
Superb! Works greatCool
you are champ Bro :)Hustler
P
4

Add a UISlider and a UILabel in Interface Builder. Create IBOutlets to access them in code and add a IBAction to respond to changes of the slider value.

Then in your code write:

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self.slider addSubview:self.label];
    [self valueChanged:self.slider];
}

- (IBAction)valueChanged:(id)sender {
    self.label.center = CGPointMake(self.slider.value*self.slider.bounds.size.width, 40);
    self.label.text = [NSString stringWithFormat:@"%0.2f", self.slider.value];
}

EDIT: To create the slider and label with code add this:

-(void)loadView {
    [super loadView];
    self.slider = [[[UISlider alloc] initWithFrame:CGRectMake(50, 150, 200, 30)] autorelease];
    self.label = [[[UILabel alloc] initWithFrame:CGRectMake(0, 0, 50, 30)] autorelease];
    [self.label setBackgroundColor:[UIColor clearColor]];
    [self.slider addTarget:self action:@selector(valueChanged:) forControlEvents:UIControlEventValueChanged];
    [self.view addSubview:self.slider];

}
Prism answered 18/5, 2011 at 16:37 Comment(9)
The only problem with this approach would be that the label won't move until the user has finished moving the slider.Zilber
Deepak, I believe that if the slider's continuous property is set to YES, that it updates as the values are changed and not just when the change is completed.Incommodious
continuous is set to YES per default when you add a slider with IBPrism
Does that make any difference if i do it directly through code and not through IB?? i would really appreciate if you give some solution with code and not with IB as on my end that would be a big mess to change the slider to work from IB.Moorhead
When i am doing self._statSlider its giving me errors saying object cannot be set -either readonly property or no setter found.Moorhead
@Moorhead you have to add a property in the @interface: @property (nonatomic, retain) IBOutlet UISlider * _statSlider; and @synthesize _statSlider in the @implementationPrism
Why do i need to do this if i am not doing it through the IB. I have defined the _statSlider in .h and using it here.Moorhead
@phix23: if you see the below example, It is showing the value of the slider but not in the bubble but below the slider.Moorhead
@Moorhead you don't have to use properties, but it makes memory management easier. Just modify the label to fit your needs. Use a smaller UIFont and place it in the slider bubble (adjust y value).Prism
R
3

You can access the rect for the slider's thumb image view with -thumbRectForBounds:trackRect:value:], so if you add a UILabel subview you can align it relative to the thumb image view in -layoutSubviews using this returned rect.

However, if you'd like to use Autolayout to align your label with the thumb image then you need access to the thumb's UIImageView directly. I'm not keen on spelunking the subviews of UIKit kit components generally, but I think you can use details of the public API to search for the image view in a future-proofed way:

- (UIImageView*) thumbImageView {
    __block UIImageView *imageView = nil;
    [self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(UIImageView *candidate, NSUInteger idx, BOOL *stop)
    {
        if ([candidate isKindOfClass:[UIImageView class]] && candidate.image ==
                [self thumbImageForState:UIControlStateNormal])
        {
            imageView = candidate;
            *stop = YES;
        }
    }];
    return imageView;
}

The API provides the ability to set a UIImage for the thumb, so you can use this as a test to ensure that you aren't getting some other image view. This is even safer if you are explicitly setting a thumb image yourself (since it's not impossible that the slider might be changed to draw the thumb image in future...). Also, the thumb image view is currently the last subview (not guaranteed going forward), so I'm doing a reverse enumeration here to hopefully get it as faster as possible whilst not making assumptions about its index.

NB: I had to create my thumb label lazily, since UISlider's subviews seem to be so.

Responsible answered 19/11, 2014 at 11:28 Comment(3)
@SandeepAggarwal which SDK?Responsible
Sorry! I was using this code before the Slider gets added, now working when I call this code snippet in layoutSubviews.Thanks!!Avifauna
Ok, that's good news. It would be strange for this to break on a non major version. UIKit tends to be rather "lazy", so not surprising to hear thisResponsible
I
0

make this nice and easy.. first off this is the final result

enter image description here

and now, code:

UILabel *sl = [UILabel new];
[v addSubview:sl];
[sl constraintHeightEqualTo:12 widthTo:26];
UIImageView *handleView = [slider.subviews lastObject];

[self.view constraintVerticalSpacingSubviewsFromBottomView:s toTopView:sl constant:10];
[self.view constraintCenterXOfView:sl equalToView:handleView];
[sl setText:@"100%"];
[sl setFont:[UIFont systemFontOfSize:10]];
self.sliderLabel = sl;

the above methods are used from an NSLayoutConstraint category i use (coming soon)

- (id)constraintHeightEqualTo:(CGFloat)height widthTo:(CGFloat)width
{
    [self setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self constraintHeightEqualTo:height];
    [self constraintWidthEqualTo:width];
    return self;
}
- (id)constraintVerticalSpacingSubviewsFromBottomView:(UIView *)fromView toTopView:(UIView *)toView constant:(CGFloat)constant

{
    NSLayoutConstraint *cn = [NSLayoutConstraint constraintWithItem:fromView
                                                          attribute:NSLayoutAttributeTop
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:toView
                                                          attribute:NSLayoutAttributeBottom
                                                         multiplier:1
                                                           constant:constant];
    [self addConstraint:cn];
    return self;
}

- (id)constraintCenterXOfView:(UIView *)fromView equalToView:(UIView *)toView constant:(CGFloat)constant
{
    NSLayoutConstraint *cn = [NSLayoutConstraint constraintWithItem:fromView
                                                          attribute:NSLayoutAttributeCenterX
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:toView
                                                          attribute:NSLayoutAttributeCenterX
                                                         multiplier:1 constant:constant];

    [self addConstraint:cn];
    return self;
}
Interstice answered 19/2, 2016 at 19:5 Comment(1)
do you have sample for this?Flatiron
D
0
- (IBAction)valueChangedSlider:(id)sender {
    handleView = [_slider.subviews lastObject];
    label = [[UILabel alloc] initWithFrame:handleView.bounds];
    label = (UILabel*)[handleView viewWithTag:1000];

    if (label==nil) {

        label = [[UILabel alloc] initWithFrame:handleView.bounds];

        label.tag = 1000;

        [label setFont:[UIFont systemFontOfSize:12]];
        label.textColor = [UIColor redColor];
        label.backgroundColor = [UIColor clearColor];

        label.textAlignment = NSTextAlignmentCenter;

        [handleView addSubview:label];


    }
    label.text = [NSString stringWithFormat:@"%0.2f", self.slider.value];
}
Duvall answered 31/5, 2016 at 13:17 Comment(0)
P
0

Well, I subclassed the UISlider and override the methods of NSControl. You just need to change your class into storyboard

I label can be add above thumb as per requirement.

class CustomSlider: UISlider {


let label = UILabel()

override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
    let track = super.beginTracking(touch, with: event)
    label.text = "\(Int(self.value))"
    label.frame = CGRect.init(x: self.thumbCenterX, y: -10, width: 20, height: 20)
    self.addSubview(label)
    return track
}


override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
    let track = super.continueTracking(touch, with: event)
    label.frame = CGRect.init(x: self.thumbCenterX - 5 , y: 6, width: 30, height: 20)
    label.text = "\(Int(self.value))"
    return track
}

override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
    super.endTracking(touch, with: event)
    label.removeFromSuperview()
}}



extension UISlider {
var thumbCenterX: CGFloat {
    let trackRect = self.trackRect(forBounds: frame)
    let thumbRect = self.thumbRect(forBounds: bounds, trackRect: bounds, value: value)
    return thumbRect.midX
}}
Pteropod answered 25/7, 2018 at 8:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.