Customized UISlider with visual value tracking

5 Comments

Introduction

This is my first blog post on programming ever. So, please don’t be too picky;-) For a project i am currently working on i had to create a custom control which looks like a normal UISlider but provides a feedback to the user on which value he or she just set by moving it. Below you see how the final implementation is actually looking (Fig. 1). As a lazy programmer i first looked around and stumbled upon similarly looking ELCSlider described in the following blog-post. But i immediately had two complaints: it seemed to heavyweight and it was utilizing (or even abusing) the mighty UIPopoverViewController to create a small popup view showing the current slider value. So, i decided to go and implement the control myself with as less coding involved as possible.

Custom slider with popup showing current value

Fig. 1

Tracking touch events

To determine when the Value-Popup subview has to be shown (or dismissed) and when it has to be updated, i have overriden following UIControl methods in the UISlider subclass implementation:

#pragma mark - UIControl touch event tracking
 
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    // Fade in and update the popup view
    CGPoint touchPoint = [touch locationInView:self];
    // Check if the knob is touched. Only in this case show the popup-view
    if(CGRectContainsPoint(self.thumbRect, touchPoint)) {
        [self _positionAndUpdatePopupView];
        [self _fadePopupViewInAndOut:YES];
    }
    return [super beginTrackingWithTouch:touch withEvent:event];
}
 
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    // Update the popup view as slider knob is being moved
    [self _positionAndUpdatePopupView];
    return [super continueTrackingWithTouch:touch withEvent:event];
}
 
- (void)cancelTrackingWithEvent:(UIEvent *)event {
    [super cancelTrackingWithEvent:event];
}
 
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    // Fade out the popoup view
    [self _fadePopupViewInAndOut:NO];
    [super endTrackingWithTouch:touch withEvent:event];
}

InĀ  beginTrackingWithTouch: it is checked if the touch entered slider’s knob boundary. Only if it is the case the popup should be shown, since there is no endTrackingWithTouch: call following otherwise (e.g. if you touch the slider’s tracks).

Deriving a Popup-View position

The custom UISlider subclass includes a method which calculates a frame rectangle of the slider knob. This rectangle is then used as basis for calculating the popup-view frame by adding some offsets here and there:

- (CGRect)thumbRect {
    CGRect trackRect = [self trackRectForBounds:self.bounds];
    CGRect thumbR = [self thumbRectForBounds:self.bounds
                                         trackRect:trackRect
                                             value:self.value];
    return thumbR;
}

Implementing a Value-Popup UIView subclass

I would not go deep into the details of this particular subclass. You can download the corresponding Xcode project for further study. Let’s just have a quick look at the drawRect: method of this UIView subclass. It creates two paths using UIBezierPath (available starting with iOS 3.2+): rounded rectangle and arrow attached to it. Both paths are merged and filled with solid color. The float value representing slider knob position is assigned to a property and cached as a string used by drawRect. The popup-view is added as subview upon UISlider subclass initialization.

- (void)drawRect:(CGRect)rect {
 
    // Set the fill color
	[[UIColor colorWithWhite:0 alpha:0.8] setFill];
 
    // Create the path for the rounded rectanble
    CGRect roundedRect = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width, self.bounds.size.height * 0.8);
    UIBezierPath *roundedRectPath = [UIBezierPath bezierPathWithRoundedRect:roundedRect cornerRadius:6.0];
 
    // Create the arrow path
    UIBezierPath *arrowPath = [UIBezierPath bezierPath];
    CGFloat midX = CGRectGetMidX(self.bounds);
    CGPoint p0 = CGPointMake(midX, CGRectGetMaxY(self.bounds));
    [arrowPath moveToPoint:p0];
    [arrowPath addLineToPoint:CGPointMake((midX - 10.0), CGRectGetMaxY(roundedRect))];
    [arrowPath addLineToPoint:CGPointMake((midX + 10.0), CGRectGetMaxY(roundedRect))];
    [arrowPath closePath];
 
    // Attach the arrow path to the rounded rectangle
    [roundedRectPath appendPath:arrowPath];
 
    [roundedRectPath fill];
 
    // Draw the text
    if (self.text) {
        [[UIColor colorWithWhite:1 alpha:0.8] set];
        CGSize s = [_text sizeWithFont:self.font];
        CGFloat yOffset = (roundedRect.size.height - s.height) / 2;
        CGRect textRect = CGRectMake(roundedRect.origin.x, yOffset, roundedRect.size.width, s.height);
 
        [_text drawInRect:textRect
                 withFont:self.font
            lineBreakMode:UILineBreakModeWordWrap
                alignment:UITextAlignmentCenter];
    }
}

Conclusion

This post shows how you can create a UISlider which shows which value is set while you move the slider’s knob. It is a lightweight and can be used as drop-in replacement wherever you used UISlider view before. However there is a room for improvements and customizations. You can use the code in your own project and modify it as you like.

Check it out on GitHub or download Xcode project: ValueTrackingSlider.zip