Tuesday, February 7, 2012

How To: Create Horizontal UIPickerView (Custom)

It's been a while since I last wrote any real tutorial. I wanted to write this tutorial for a while, but was delayed due to updating my own apps, and creating new apps, and also due to relatives who got seriously ill, yadda yadaa, whoop dee doo whoop dee dye and so forth.



Anyway, today I'd like to show you how you can create a custom sized horizontal UIPickerView. UIPickerView is an awesome object to display a list of items. While you can accomplish this using UIScrollView easily, the behaviour of UIPickerView is slightly better because the items in UIPickerView is auto selected when user chooses it - that cool spring effect centers the selection nicely at as well.



So lets get started, first of all, all you need to do is drop a UIPickerView object on your view in the Interface Builder. Then in your .h file input:


#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UIPickerViewDelegate> {

    IBOutlet UIPickerView *pickerView;

    NSMutableArray *itemArray;

}

@property (nonatomic, retain)  UIPickerView *pickerView;

@end

Basically we're just declaring our UIPickerView as IBOutlet (nothing new in declaration method here). We also added a NSMutableArray so that we can manipulate our items and be able to add our items in the pickerview easily later. We also add UIPickerViewDelegate at the interface because we will be using the built in Delegate functions of UIPickerView object.

Hang on a minute, what the heck is an NSMutableArray? If you are familiar with some basic programming I am sure that you are familiar with an array. An array is a defined quantity of collection of data. For eg:

myFish[14] holds 15 variables. from 0 to 14. (Remember index of an arrays always start with 0).
So you can access them by myFish[0] = Tetras; myFish[1] = Rasboras. And so on. But you are limited to 15 fishes. This is where NSMutableArray differs, an NSMutableArray is an array that is mutable, or expandable/changeable. So if you declare myFish as an NSMutableArray, then you can have up to whatever value you wish, so long as you be careful not to overload it (memory issues).

Back to the tutorial:

So now you have declared the UIPickerView, go to Interface Builder and connect BOTH the "delegate" and "Referencing Outlet" to the FileOwner.

Next, we go to .m file and synthesize the UIPickerView. Also we'd want to add the UIPickerView delegate methods as below (read the comments for each delegate's purposes:


- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)thePickerView {

return 1;

}

- (NSInteger)pickerView:(UIPickerView *)thePickerView numberOfRowsInComponent:(NSInteger)component {

return [itemArray count];

}


- (UIView *)pickerView:(UIPickerView *)thePickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view

{

return [itemArray objectAtIndex: row];

}

- (void)pickerView:(UIPickerView *)thePickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {



}


numberOfComponentsInPickerView
This delegate is easy to implement, just type "return #;" where # is the number of components
you want. "Component" is the scrollable object in a pickerview. For example, a date pickerview has 3 components where user can select each of date, month and year. For our case, we are going to use just 1 component.

numberOfRowsInComponent
This delegate you need to return the number of items in each component. If you have multiple components,
you need to use switch (or if) statement to specify for each components. In our case we just have 1 component, so we only return the item count of our array.

viewForRow
This delegate is where you specify the "VIEW"/"Object" of the item. Since we store our items in
array, we just need to return the object by using: [itemArray objectAtIndex:row];

didSelectRow
This delegate is always called when user selects an item. Write the actions you'd like to happen when user select something in here.

Remember that delegate functions must be written as it is, EXACTLY. Any deviation might cause it not to work. Do check Apple docs for the latest delegate function names in case your implementation does not work.

Next is the fun part, customizing the UIPickerView. We will add the customization code in the viewDidLoad as we want it to be customized after the view is loaded. Write the codes below in the viewDidLoad.

// set the pickerview delegate to itself. This is important because if you don't set

// this, then the delegate functions will not work/be called.

 self.pickerView.delegate = self;

// here is where the customization lies: CGAffineTransform is a way to transform

// object according to scale and rotation. Here we rotate the pickerview by PI/2

// which is 90 degrees in radians. Then we concat the rotation transform with

// scale transform, and finally setting the pickerview transform.

CGAffineTransform rotate = CGAffineTransformMakeRotation(3.14/2);

 rotate = CGAffineTransformScale(rotate, 0.1, 0.8);

 [self.pickerView setTransform:rotate]; 

// set the center location.

 self.pickerView.center = CGPointMake(160,75);

  // Here I decided to add UILabel as the item's "object"

// you can use ANYTHING here, like UIImageViews or any class of UIView

// Since we rotate the pickerview in one direction, we need to compensate

// the item's angle by counter rotating it in the opposite direction,

// and adjust the scale as well. You may need to try a few times to get

// the right/suitable size as for the scale.

 UILabel *theview[20];

 CGAffineTransform rotateItem = CGAffineTransformMakeRotation(-3.14/2);

 rotateItem = CGAffineTransformScale(rotateItem, 1, 10);

 // next alloc and create the views in a loop. here I decided to have 20

// UIlabels, each with a text of 1 to 20. Set the other UIlabel's property as you wish.

 for (int i=1;i<=20;i++) { 

  theview[i] = [[UILabel alloc] init];

  theview[i].text = [NSString stringWithFormat:@"%d",i];

  theview[i].textColor = [UIColor blackColor];

  theview[i].frame = CGRectMake(0,0, 100, 100);

  theview[i].backgroundColor = [UIColor clearColor];

  theview[i].textAlignment = UITextAlignmentCenter;

  theview[i].shadowColor = [UIColor whiteColor];

  theview[i].shadowOffset = CGSizeMake(-1,-1);

  theview[i].adjustsFontSizeToFitWidth = YES;

    UIFont *myFont = [UIFont fontWithName:@"Georgia" size:15];

  [theview[i] setFont:myFont];

    theview[i].transform = rotateItem;

 }

    

    // then we initialize and create our NSMutableArray, and add all 20 UIlabel views

// that we just created above into the array using "addObject" method.

   itemArray = [[NSMutableArray alloc] init];

  for (int j=1;j<=20;j++) {

       

        [itemArray addObject:theview[j]];

   }

That is all there is to it. Run your app and it should display a nice horizontal UIPickerView!
How about have it do something when you select an item? Easy. Go back to .h and add another IBOutlet of UILabel *myLabel. Go to your XIB file in IB and add a label and connect the Outlet to FileOwner as myLabel. Goto .m file and synthesize myLabel. Then goto UIPickerView delegate called didSelectRow and add the following line:
myLabel.text = [NSString stringWithFormat:@"SELECTED: %d", row+1];
Now when you select a row, the label will show you which row you selected. Cool eh?
Well, that's it for now. Hope this tutorial helped someone. Good luck!

3 comments:

  1. Great!
    Many Thanks for You tutorial!
    I used this to make a colored stylish PickerView (not horizontal).

    But when I change the background color of the UILabel to have a colored picker, the text output is damaged. The letters are limited to single lines on the downside. Only the very 1st disappearing entry on the top is readable.

    Can You imagine why?
    Seems to be a limitation of this technique.

    ReplyDelete
  2. @abtzero, I believe it has something to do with the CGAffineTransform. If you are not doing it vertically, I suggest you remove all the .transform method in the viewDidLoad method (based on this project code)

    ReplyDelete
  3. that was great help to start me off.

    How do I use a second component, what is the if statement to set the rows and how do i load it.
    thanks fot the help.

    ReplyDelete