Adding a UIPopover to UISlider

October 29th, 2010 Posted by: - posted under:Featured » Snippets

Overview

If you have an iPad you have probably used iBooks, Apple’s eBook application that gives users access to the iBooks store. In this application you can navigate through books in a number of ways. Today we are going to focus on the scroll bar at the bottom of a book that a user can utilize to skip to any given page within the book. This control involves a customized UISlider and a UIPopoverView that drags along with the slider as the value changes. Today we will be making a UISlider subclass that will duplicate this functionality.

How to Use

ELCSlider Demo from Collin Ruffenach on Vimeo.

So that is it! Put a UISlider in your XIB and then change its class to ELCSider. You can change the range of the slider through interface builder and the slider will work the same way. If you would like to change the appearance of the pop over, the number of digits that are shown or anything else you will need to change a bit of code. Look below for how the magic of this subclass is done.

GitHub

You can find this project now on GitHub. Please let me know any issues you may have. Happy coding!

How its Made

The ELCSlider package is a collection of 5 files. 2 classes and 1 XIB. The first class that we will look at is the ELCSlider class which is a UISlider subclass. First taking a look at the header, we declare a class property PopoverController and SliderValueViewController. You can see the code below.

 
 
#import "SliderValueViewController.h"
 
@interface ELCSlider : UISlider {
 
	UIPopoverController *popoverController;
	SliderValueViewController *sliderValueController;
}
 
@end

With the simple header declared we can go into the main of the ELCSlider. The first method you see in the main is the initWithCoder: method. This gets us into an interesting part of the iOS SDK that I we should investigate quickly. Looking at where initWithCoder: is declared, we see that it is a part of the NSCoding Protocol. This method is passed an archived object and is essentially to take saved files and turn them back into Objective C objects. If you ever create a custom class, and you want to save an instance of that class to a file, you will need to have that class conform to the NSCoding Protocol. If we look down the inheritance chain of a UISlider we will see that UIView indeed conforms to this protocol. This is because all UIView’s are going to need to archive and unarchive themselves from a .XIB file. As a result of this, when you lay out an element in a XIB the initialization method that will be called will be initWithCoder. Within the method we ensure that our super class initialized properly, and then we proceed to assign the method valueChanged: to the control event UIControlEventValueChanged. This will make a call to the valueChanged method to let us handle moving the UIPopoverView around based on the current value of the slider. After that we create an instance of our SliderValueViewController, which we will go through in the next section, and create a UIPopoverController with the content view controller being our SliderValueViewController. You can see the code for our initialization method below.

-(id)initWithCoder:(NSCoder *)aDecoder {
	if(self = [super initWithCoder:aDecoder]) {
 
		[self addTarget:self action:@selector(valueChanged:) forControlEvents:UIControlEventValueChanged];
 
		sliderValueController = [[SliderValueViewController alloc] initWithNibName:@"SliderValueViewController" bundle:[NSBundle mainBundle]];
		popoverController = [[UIPopoverController alloc] initWithContentViewController:sliderValueController];
		[popoverController setPopoverContentSize:sliderValueController.view.frame.size];
	}
 
    return self;
}

The only remaining method to make is the method to handle what happens when the slider value changes. This method is all about doing the math to calculate the CGRect where we want out UIPopoverView to show itself. This is a value that will be derived based on the x origin of the slider, the slider width, the slider’s max and min, the current value and the size of the slider circular button (22px). I wrote this class so that it supports any type of slider range, positive to positive, negative to positive and negative to negative. The math is kind complex to figure out exactly where to display the pop over, but you can take a look at the source if you are curious. The real trick was measuring the width of the UISlider circular button as the center of it doesn’t go all the way to the edge of the slider, you you need to add or subtract some small amount to the x coordinate you compute depending on the value the slider is reporting. You can see the method below.

-(void)valueChanged {
 
	[sliderValueController updateSliderValueTo:self.value];
 
	CGFloat sliderMin =  self.minimumValue;
	CGFloat sliderMax = self.maximumValue;
	CGFloat sliderMaxMinDiff = sliderMax - sliderMin;
	CGFloat sliderValue = self.value;
 
	if(sliderMin < 0.0) {
 
		sliderValue = self.value-sliderMin;
		sliderMax = sliderMax - sliderMin;
		sliderMin = 0.0;
		sliderMaxMinDiff = sliderMax - sliderMin;
	}
 
	CGFloat xCoord = ((sliderValue-sliderMin)/sliderMaxMinDiff)*[self frame].size.width-sliderValueController.view.frame.size.width/2.0;
 
	CGFloat halfMax = (sliderMax+sliderMin)/2.0;
 
	if(sliderValue > halfMax) {
 
		sliderValue = (sliderValue - halfMax)+(sliderMin*1.0);
		sliderValue = sliderValue/halfMax;
		sliderValue = sliderValue*11.0;
 
		xCoord = xCoord - sliderValue;
	}
 
	else if(sliderValue <  halfMax) {
 
		sliderValue = (halfMax - sliderValue)+(sliderMin*1.0);
		sliderValue = sliderValue/halfMax;
		sliderValue = sliderValue*11.0;
 
		xCoord = xCoord + sliderValue;
	}
 
	[popoverController presentPopoverFromRect:CGRectMake(xCoord, 0, sliderValueController.view.frame.size.width, sliderValueController.view.frame.size.height) inView:self permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES];
}

Creating the Popover’s Content View Controller

Now that we have all of the logic in place to move the view controller appropriately, we need to create the view controller that will be injected into the Popover. The view controller I have created is meant to be restyled, refactored or even replaced. The view controller has one required method, updateSliderValueTo:. This method is called every time the slider value changes. People may use this method to change other aspects of the content view controller, such as background color, or possibly other labels. Please feel free to customize as much as you like.

GitHub

You can find this project now on GitHub. Please let me know any issues you may have. Happy coding!

Follow me on twitter @cruffenach