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