Introduction

Required images

Here you find a customizable PickerView, which is limited to text. You need at least 4 pictures for the pickerView:

  • a backgroundView for the whole picker
  • a backgroundView for every picker item
  • an image for the selected item in the picker item
  • an overlay image for the picker item, you can use this, to darken the top most and bottom most elements

UIImage (Some hints for the images)

Required objects

  • Titles for every picker item (The PickerView can have multiple pickers) as NSStrings in an NSArray
  • items, which should be in a picker item (As a NSArray of NSStrings, stored in a NSDictionary, the keys should be equal to the titles)
  • the frame for every picker item (As a CGRect stored as a NSValue in a NSArray)
  • selected items in every picker item (As a NSNumber stored in a NSArray)
  • the height of every item in the picker item

For example with two pickers in the pickerView:

NSArray* titles=[NSArray arrayWithObjects: @"Item1", @"item2", nil];
NSArray* txt1=[NSArray arrayWithObjects:@"1",@"6",@"11",@"16",@"21",@"26",nil];
NSArray* txt2=[NSArray arrayWithObjects:@"Jan",@"Mar",@"Mai",@"Jul",@"Sep",@"Nov",nil];
NSDictionary* items=[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects: txt1 ,txt2, nil] forKeys:titles];
NSArray* frameTitles=[NSArray arrayWithObjects:[NSValue valueWithCGRect: CGRectMake(10, 40, 60, 130)], [NSValue valueWithCGRect: CGRectMake(80, 40, 70, 130)],nil];
NSArray* selectedArray=[NSArray arrayWithObjects:[NSNumber numberWithInt:0],[NSNumber numberWithInt:0],nil];
float height=35;
PickerView* picker=[[PickerView alloc] initWithFrame:CGRectMake(0,460-185,320,185) arrayOfTitles: titles withDictionaryOfText: items andWithFrameOfPickerViews:frameTitles withSelectedIndexes: selectedArray withHeightOfItem:35];

Sourcecode

PickerView.h

#import <UIKit/UIKit.h>

//delegate protocol
@protocol PickerViewDelegate
	//NSDictiory has the array index from 0 to ... as key, and the NSString value as the object stored
	//If you initialized the PickerView with two array object, the dictionary consists of two items with the keys 0 and 1 (as NSNumber)
	-(void) userSelectedTheItems:(NSDictionary*)selectedItems andSelectedIndexes:(NSDictionary*)selectedIndexes;
@end


@interface PickerView : UIView<UIScrollViewDelegate> {
	NSMutableDictionary* selectedItems;
	NSMutableDictionary* selectedIndexes;
	NSArray* titleArray;
	NSDictionary* txtDictionary;
	CGFloat heightOfItem;
	id<PickerViewDelegate> delegate;
	UIView* baseView;
	CGPoint lastPoint;
}

@property (assign) id<PickerViewDelegate> delegate;

#pragma mark chaging the views and its properties

/*	Method sets the background view of the full Pickerview
 
	You SHOULD NOT PASS NUll or nil*/
-(void) setBackgroundView:(UIView *) view;

/*	Method sets the background views for the pickers
 
 	bgViews		an array of background views for the pickers
 
 	YOU should have for every Picker one background view
 */
-(void) setBackgroundViews: (NSArray*) bgViews;

/*	Method sets the selected background views for the pickers
 
	bgViews		an array of selected background views for the pickers
 
	YOU should have for every Picker one background view
 */
-(void) setSelectedBackgroundViews: (NSArray*) bgViews;

/*	Method sets the overlay views for the pickers
 
	overlayViews		an array of overlay views for the pickers
 
	YOU should have for every Picker one overlay view
 */
-(void) setOverlayViews: (NSArray*) overlayViews;

/*	Method sets the font and the textColor for the titleLabel of the scrollview
 
	 theFont		the font for the titleLabel
	 theColor		the text color for the title lable
	 theIndex		which title label should get the new properties
					If theIndex is smaller than 0 or bigger or equal to the number of pickers, all title labels get this new properties
 */
-(void) setFont:(UIFont*) theFont withColor:(UIColor*) theColor forTitleForScrollViewIndex:(int) theIndex;

/*	Method sets the font and the textColor for the titleLabel of the scrollview
 
	 theFont		the font for the items in the picker
	 theColor		the text color for the items in the picker
	 theIndex		which  picker items  should get the new properties
					If theIndex is smaller than 0 or bigger or equal to the number of pickers, all picker items get this new properties
 */
-(void) setFont:(UIFont*) theFont withColor:(UIColor*) theColor forItemsInScrollViewIndex:(int) theIndex;

#pragma mark init

/*	Method, which initialized the pickerView
 
	 frame				The frame picker view
	 titArray			Array of titles for every picker, Should always be NSStrings, the first item will be the first picker title
	 textDictionary		keys are equal to the values from titArray
						objects should be Arrays of NSStrings
	 frameTitles		frames for every PickerView, Should be an CGRect stored in an NSValue
	 selectedArray		Array of selected items, should be NSNumbers, length should be equal to [titArray count]
	 theHeightOfItem	the height of every label in the picker
 */
- (id)initWithFrame:(CGRect)frame arrayOfTitles:(NSArray*) titArray withDictionaryOfText: (NSDictionary*) textDictionary andWithFrameOfPickerViews:(NSArray*) frameTitles withSelectedIndexes:(NSArray*) selectedArray withHeightOfItem:(CGFloat) theHeightOfItem;
@end

PickerView.m

#import "PickerView.h"
@interface PickerView()
	@property (retain) NSArray* titleArray;
	@property (retain) NSDictionary* txtDictionary;
	@property (retain) NSMutableDictionary* selectedItems;
	@property (retain) NSMutableDictionary* selectedIndexes;
	@property (retain) UIView* baseView;
	@property (assign) CGFloat heightOfItem;
@end

@implementation PickerView
@synthesize delegate, selectedItems,selectedIndexes, titleArray, txtDictionary;
@synthesize heightOfItem;
@synthesize baseView;

#pragma mark changing views of pickerView

/*	Method sets the background view of the full Pickerview
	
	You SHOULD NOT PASS NUll or nil
 */
-(void) setBackgroundView:(UIView *) view{
	//Check, if view is NULL or nil
	if (!view) {
		return;
	}
	//Check if view is an UIView or a subclass of it
	if (![view isKindOfClass:[UIView class]]) {
		return;
	}
	//If backgroundView is not nil, add it to the view
	UIView* bg=[self viewWithTag:10009];
	if (bg) {
		CGRect rec=bg.frame;
		view.frame=rec;
		view.tag=10009;
		[self insertSubview:view belowSubview:bg];
		[bg removeFromSuperview];
	}
}

/*	Method sets the background views for the pickers
 
	bgViews		an array of background views for the pickers
 
	YOU should have for every Picker one background view
 */
-(void) setBackgroundViews: (NSArray*) bgViews{
	//If bgViews count is not equal to the picker view count
	if ([bgViews count] !=[self.titleArray count]) {
		return;
	}
	for (int i=0; i<[self.titleArray count]; i++) {
		UIView* newView=[bgViews objectAtIndex:i];
		if (!newView) {
			return;
		}
		int index=10004+i*10;
		UIView* v=[self.baseView viewWithTag:index];
		newView.frame=v.frame;
		[v.superview insertSubview:newView belowSubview:v];
		[v removeFromSuperview];
	}		
}

/*	Method sets the selected background views for the pickers
	
	bgViews		an array of selected background views for the pickers
				
	YOU should have for every Picker one selected background view
 */
-(void) setSelectedBackgroundViews: (NSArray*) bgViews{
	//If bgViews count is not euqal to the picker view count
	if ([bgViews count] !=[self.titleArray count]) {
		return;
	}
	for (int i=0; i<[self.titleArray count]; i++) {
		UIView* newView=[bgViews objectAtIndex:i];
		if (!newView) {
			return;
		}
		int index=10002+i*10;
		UIView* v=[self.baseView viewWithTag:index];
		newView.frame=v.frame;
		[v.superview insertSubview:newView belowSubview:v];
		[v removeFromSuperview];
	}		
}

/*	Method sets the overlay views for the pickers
 
	overlayViews		an array of overlay views for the pickers
 
	YOU should have for every Picker one overlay view
 */
-(void) setOverlayViews: (NSArray*) overlayViews{
	//If overlayViews count is not euqal to the picker view count
	if ([overlayViews count] !=[self.titleArray count]) {
		return;
	}
	
	for (int i=0; i<[self.titleArray count]; i++) {
		UIView* newView=[overlayViews objectAtIndex:i];
		if (!newView) {
			return;
		}
		int index=10006+i*10;
		UIView* v=[self.baseView viewWithTag:index];
		newView.frame=v.frame;
		[v.superview insertSubview:newView belowSubview:v];
		[v removeFromSuperview];		
	}	
}

/*	Method sets the font and the textColor for the titleLabel of the scrollview
	
	theFont		the font for the titleLabel
	theColor	the text color for the title lable
	theIndex	which title label should get the new properties
					If theIndex is smaller than 0 or bigger or equal to the number of pickers, all title labels get this new properties
	*/
-(void) setFont:(UIFont*) theFont withColor:(UIColor*) theColor forTitleForScrollViewIndex:(int) theIndex{
	//Check if theFont is a font
	if (![theFont isKindOfClass:[UIFont class]]) {
		return;
	}
	//Check if the colors are set
	if (![theColor isKindOfClass:[UIColor class]]) {
		return;
	}
	if (theIndex>=0 &&theIndex<[self.titleArray count]) {
		UILabel* l=(UILabel*)[self.baseView viewWithTag:10008+theIndex*10];
		l.font=theFont;
		l.textColor=theColor;
	}
	else {
		for (int i=0; i<[self.titleArray count]; i++) {
			UILabel* l=(UILabel*)[self.baseView viewWithTag:10008+i*10];
			l.font=theFont;
			l.textColor=theColor;
		}
	}
}

/*	Method sets the font and the textColor for the titleLabel of the scrollview
 
	 theFont		the font for the items in the picker
	 theColor		the text color for the items in the picker
	 theIndex		which  picker items  should get the new properties
						If theIndex is smaller than 0 or bigger or equal to the number of pickers, all picker items get this new properties
 */
-(void) setFont:(UIFont*) theFont withColor:(UIColor*) theColor forItemsInScrollViewIndex:(int) theIndex{
	//Check if theFont is a font
	if (![theFont isKindOfClass:[UIFont class]]) {
		return;
	}
	//Check if the colors are set
	if (![theColor isKindOfClass:[UIColor class]]) {
		return;
	}
	if (theIndex>=0 &&theIndex<[self.titleArray count]) {
		UIScrollView* sView=(UIScrollView*)[self.baseView viewWithTag:10000+theIndex*10];
		UIView* subView=[sView.subviews objectAtIndex:0];
		for (int i=0; i<[subView.subviews count]; i++) {
			UILabel* l=(UILabel*)[subView.subviews objectAtIndex:i];
			l.font=theFont;
			l.textColor=theColor;
		}		
	}
	else {
		for (int i=0; i<[self.titleArray count]; i++) {
			UIScrollView* sView=(UIScrollView*)[self.baseView viewWithTag:10000+i*10];
			UIView* subView=[sView.subviews objectAtIndex:0];
			for (int j=0; j<[subView.subviews count]; j++) {
				UILabel* l=(UILabel*)[subView.subviews objectAtIndex:j];
				l.font=theFont;
				l.textColor=theColor;
			}
		}
	}
}

/*	Method creates the picker, with the title, scrollview and its items
	
	txtArray		Array of NSStrings, which should be in the picker
	theTitle		the title of the picker
	theFrame		the Frame of the picker (without titleLabel)
	inSubview		the superview for the picker
	theTag			the base tag for the views
						0	scrollView
						2	selectedBG
 						4	background for picker
 						6	overlay background
 						8	titleLabel
	index			the selected index of txtArray
 */
-(void) createPickerWithTextArray:(NSMutableArray*) txtArray andTitleForIt:(NSString*) theTitle inFrame:(CGRect)theFrame inSubview:(UIView*)theBaseView withTag:(int) theTag andArrayIndex:(int) index{
	//Title
	UILabel* l=[[UILabel alloc] initWithFrame:CGRectMake(theFrame.origin.x, theFrame.origin.y*0.1, theFrame.size.width, theFrame.origin.y*0.9)];
	l.backgroundColor=[UIColor clearColor];
	l.textAlignment=UITextAlignmentCenter;
	l.text=theTitle;
	l.tag=theTag+8;
	[theBaseView addSubview:l];
	[l release];
	//Background
	UIImageView* bgView=[[UIImageView alloc] initWithFrame:theFrame];
	bgView.image=[[UIImage imageNamed:@"ItemBG.png"] stretchableImageWithLeftCapWidth:15 topCapHeight:15];
	bgView.tag=theTag+4;
	bgView.backgroundColor=[UIColor clearColor];
	[theBaseView addSubview:bgView];
	[bgView release];
	
	//ScrollView with content
	UIView* labelView=[[UIView alloc] initWithFrame:CGRectMake(0, 0, theFrame.size.width-10, theFrame.size.height*[txtArray count])];
	labelView.tag=theTag;
	UIScrollView* scrollView=[[UIScrollView alloc] initWithFrame:CGRectMake(theFrame.origin.x+5, theFrame.origin.y, theFrame.size.width-10, theFrame.size.height)];
	scrollView.tag=theTag;
	scrollView.delegate=self;
	scrollView.contentSize=CGSizeMake(theFrame.size.width-10, self.heightOfItem*[txtArray count]);
	scrollView.backgroundColor=[UIColor clearColor];
	scrollView.showsHorizontalScrollIndicator=NO;
	scrollView.showsVerticalScrollIndicator=NO;
	scrollView.contentInset=UIEdgeInsetsMake((theFrame.size.height-self.heightOfItem)/2.0+self.heightOfItem, 0, (theFrame.size.height-self.heightOfItem)/2.0-self.heightOfItem, 0);
	
	//Labels
	for (int i=0; i<[txtArray count]; i++) {
		UILabel* l=[[UILabel alloc] initWithFrame:CGRectMake(0, i*self.heightOfItem-self.heightOfItem, theFrame.size.width-10, self.heightOfItem)];
		l.text=[txtArray objectAtIndex:i];
		l.backgroundColor=[UIColor clearColor];
		l.textAlignment=UITextAlignmentCenter;
		l.userInteractionEnabled=YES;
		[labelView addSubview:l];
		[l release];
	}	
	UIImageView* overlayView1=[[UIImageView alloc] initWithFrame:theFrame];
	overlayView1.image=[[UIImage imageNamed:@"ItemBG.png"] stretchableImageWithLeftCapWidth:15 topCapHeight:40];
	overlayView1.backgroundColor=[UIColor clearColor];
	overlayView1.tag=theTag+6;
	UIImageView* selectedView1=[[UIImageView alloc] initWithFrame:CGRectMake(theFrame.origin.x, theFrame.origin.y+(theFrame.size.height-self.heightOfItem)/2.0, theFrame.size.width, self.heightOfItem )];
	selectedView1.image=[[UIImage imageNamed:@"ItemBG.png"] stretchableImageWithLeftCapWidth:13 topCapHeight:13];
	selectedView1.backgroundColor=[UIColor clearColor];
	selectedView1.tag=theTag+2;
	
	[scrollView addSubview:labelView];
	[theBaseView addSubview:scrollView];
	[theBaseView addSubview:overlayView1];
	[theBaseView addSubview:selectedView1];
	
	scrollView.contentOffset=CGPointMake(0, -((theFrame.size.height-self.heightOfItem)/2.0)-self.heightOfItem+index*self.heightOfItem);
	
	[labelView release];
	[scrollView release];
	[overlayView1 release];
	[selectedView1 release];
}

#pragma mark UIScrollViewDelegate and its helpermethods in this class

/*	Method calculates the new offset point
	
	scrollView		the scrollview, which content offset should be edited
	yesOrNo			if the selected items and indexes should updated
 */
-(NSValue*) placeContentOfScrollView:(UIScrollView *)scrollView canInformDelegate:(Boolean) yesOrNo{
	CGPoint p=scrollView.contentOffset;
	float theOffset= -((scrollView.frame.size.height-self.heightOfItem)/2.0)-self.heightOfItem;
	float pY=p.y-theOffset;
	
	int intCalculation=pY/self.heightOfItem;
	if (lastPoint.y<p.y) {
		intCalculation++;
		pY=intCalculation*self.heightOfItem+theOffset;
	}
	else {
		pY=intCalculation*self.heightOfItem+theOffset;
	}
	UIView* labelView=[scrollView.subviews objectAtIndex:0];	
	NSArray* ar=[labelView subviews];
	if (intCalculation<0) {
		intCalculation=0;
		pY=pY+ABS(intCalculation)*self.heightOfItem;
	}
	if (intCalculation>=[ar count]) {
		int backOffset=intCalculation-([ar count]-1);
		intCalculation=[ar count]-1;
		pY=pY-backOffset*self.heightOfItem;
	}
	if (yesOrNo) {
		int theTag=scrollView.tag-10000;
		theTag/=10;
		UILabel* l=[ar objectAtIndex:intCalculation];		
		[self.selectedIndexes setObject:[NSNumber numberWithInt:intCalculation] forKey:[NSNumber numberWithInt:theTag]];
		[self.selectedItems setObject:l.text forKey:[NSNumber numberWithInt:theTag]];
		[self.delegate userSelectedTheItems:self.selectedItems andSelectedIndexes:self.selectedIndexes];
	}	
	return [NSValue valueWithCGPoint:CGPointMake(p.x, pY)];
}

/*	delegate method, if the user begins to dragging the scrollview
	
	saves a new lastPoint
 */
- (void) scrollViewWillBeginDragging:(UIScrollView *)scrollView{
		lastPoint=scrollView.contentOffset;
}

/*	Delegate method, when the scroll view  did end decelerating
	
	Calculate the new offset point and updates the selected indexes and items
 */
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
	NSValue* x = [self placeContentOfScrollView:scrollView canInformDelegate:YES];
	[scrollView setContentOffset: [x CGPointValue]  animated:YES];
}

/*	Delegate method, when the scroll view  did end dragging with decelerating or not
 
	Calculate the new offset point; updates the selected indexes and items, if decelerating is NO
 */
-(void) scrollViewDidEndDragging:(UIScrollView*) scrollView willDecelerate:(BOOL) decelerate{
	NSValue* x = [self placeContentOfScrollView:scrollView canInformDelegate:!decelerate];
	[scrollView setContentOffset: [x CGPointValue]  animated:YES];
}

#pragma mark init/dealloc

/*	Method, which initialized the pickerView
	
	frame				The frame picker view
	titArray			Array of titles for every picker, Should always be NSStrings, the first item will be the first picker title
	textDictionary		keys are equal to the values from titArray
						objects should be Arrays of NSStrings
	frameTitles			frames for every PickerView, Should be an CGRect stored in an NSValue
	selectedArray		Array of selected items, should be NSNumbers, length should be equal to [titArray count]
	theHeightOfItem		the height of every label in the picker
 */
- (id)initWithFrame:(CGRect)frame arrayOfTitles:(NSArray*) titArray withDictionaryOfText: (NSDictionary*) textDictionary andWithFrameOfPickerViews:(NSArray*) frameTitles withSelectedIndexes:(NSArray*) selectedArray withHeightOfItem:(CGFloat) theHeightOfItem{
	self = [super initWithFrame:frame];
    if (self) {
		//Check if count is equal
		if (!([titArray count] == [textDictionary count] && [textDictionary count] == [frameTitles count]&& [frameTitles count] == [selectedArray count])) {
			return self;
		}
		//Checks if titAray consits of NSStrings, dictionary has an array for it, selectedArray has valid objects
		for (int i=0; i<[titArray count];i++) {
			NSString* s=[titArray objectAtIndex:i];
			if (![s isKindOfClass:[NSString class]]) {
				return self;
			}
			NSArray* ar=[textDictionary objectForKey:s];
			if (!ar) {
				return self;
			}
			if (![[selectedArray objectAtIndex:i] isKindOfClass:[NSNumber class]]) {
				return self;
			}
			NSNumber* selected=[selectedArray objectAtIndex:i];
			if ([selected intValue]<0 ||[selected intValue]>=[ar count] ) {
				return self;
			}
		}
		//init the view
		self.txtDictionary=textDictionary;
		self.titleArray=titArray;
        self.opaque = NO;
		self.backgroundColor = [UIColor clearColor];
		NSMutableDictionary* dic=[[NSMutableDictionary alloc] init];
		self.selectedItems=dic;
		[dic release];
		NSMutableDictionary* dic2=[[NSMutableDictionary alloc] init];
		self.selectedIndexes=dic2;
		[dic2 release];
		self.heightOfItem=theHeightOfItem;
		
		UIImageView* imgView=[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
		imgView.tag=10009;
		imgView.image=[[UIImage imageNamed:@"ItemBG.png"] stretchableImageWithLeftCapWidth:15 topCapHeight:15];
		//Create the pickers
		UIView* baseView1=[[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
		for (int i=0; i<[self.titleArray count]; i++) {
			NSNumber* selected=[selectedArray objectAtIndex:i];
			[self.selectedIndexes setObject:selected forKey:[NSNumber numberWithInt:i]];
			int selectedIndex=[selected intValue];
			NSString* key=[self.titleArray objectAtIndex:i];
			NSMutableArray* items= [NSMutableArray arrayWithArray:[self.txtDictionary objectForKey:key]];
			[self.selectedItems setObject:[items objectAtIndex:selectedIndex] forKey:[NSNumber numberWithInt:i]];
			NSValue* ptVal=[frameTitles objectAtIndex:i];
			CGRect rec=[ptVal CGRectValue];
			[self createPickerWithTextArray:items andTitleForIt:key inFrame:rec inSubview:baseView1 withTag:10000+i*10 andArrayIndex:selectedIndex];
		}
		[self addSubview:imgView];
		[self addSubview:baseView1];
		self.baseView=baseView1;
		[baseView1 release];
		[imgView release];
    }
    return self;
}

/*	dealloc method
 */
- (void)dealloc {
	self.delegate=nil;
	self.titleArray=nil;
	self.txtDictionary=nil;
	self.selectedItems=nil;
	self.selectedIndexes=nil;
	self.baseView=nil;
    [super dealloc];
}
@end