May 2, 2013

UIScrollView With Page Numbers (UIPageControl-like)


When using UIScrollView in paging mode, it is quite common to incorporate it with UIPageControl for simpler navigation. This tutorial shows how to create completely custom page control with page numbers which works very similar to UIPageControl

Here's how final result looks like:

1. Setting up the project 

Open XCode and create new project using Single View Application template: 

Name the project, select "iPhone" option from "Devices" menu, make sure that "Use Automatic Reference Counting" is checked, press "Next" one more time and save your project.

2. Adding graphics

In order to create custom page control, you will need two pictures which would be used for disabled/enabled page background similar to these:

(note that @2x version of the files should be added for Retina display support)

3. Adding functionality 

Setting up ViewController.h

We will need to create scroll view and an array to store page control elements. Also we will need to access UIScrollView's delegate method, so here's how file should look like:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UIScrollViewDelegate>
    UIScrollView* scrollView;

    NSMutableArray *PageControlBtns;

Setting up ViewController.m

First, let's define pages number at the top of the file:

#define PAGES_NUMBER 6

Next, let's modify viewDidLoad function by adding scroll view and calling GeneratePages function which is going to generate page control:

- (void)viewDidLoad
    [super viewDidLoad];
    scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(50.0, 50.0, self.view.frame.size.width-100.0, self.view.frame.size.width-100.0)];
    scrollView.pagingEnabled = YES;
    scrollView.showsHorizontalScrollIndicator = NO;
    scrollView.delegate = self;
    scrollView.backgroundColor = [UIColor clearColor];
    [self.view addSubview:scrollView];
    [self GeneratePages];

Now we are going to actually generate pages in loop, which includes following:

  • Creating UIView (Page) with label which displays current page number, and adding this page to scroll view
  • Creating UIButton with page number label which represents page control element, adding this button to view, and storing it in the array. Each button has appropriate tag, so that when onPageControlBtnPressed: is called, we will know exactly which button was pressed.
Here's function implementation:

    UIImage *PageControlImg = [UIImage imageNamed:@"page_deselected.png"];
    int x_coord = (self.view.frame.size.width-PageControlImg.size.width*PAGES_NUMBER)/2.0;
    PageControlBtns = [[NSMutableArray alloc] init];
    for (int i=0; i<PAGES_NUMBER; i++)
        UIView *Page = [[UIView alloc] initWithFrame:CGRectMake(scrollView.frame.size.width*i, 0.0, scrollView.frame.size.width, scrollView.frame.size.height)];
        Page.backgroundColor = [UIColor colorWithRed:0.32 green:0.55 blue:0.88 alpha:1.0];
        UILabel *titleLbl = [[UILabel alloc] initWithFrame:CGRectMake(10.0, 10.0, Page.frame.size.width-20.0, 40.0)];
        titleLbl.backgroundColor = [UIColor clearColor];
        titleLbl.textColor = [UIColor whiteColor];
        titleLbl.font = [UIFont boldSystemFontOfSize:16.0];
        titleLbl.text = [NSString stringWithFormat:@"This is page number %d", i+1];
        titleLbl.textAlignment = UITextAlignmentCenter;
        [Page addSubview:titleLbl];
        [scrollView addSubview:Page];
        UIButton *PageControlBtn = [[UIButton alloc] initWithFrame:CGRectMake(x_coord+i*PageControlImg.size.width, scrollView.frame.origin.y+scrollView.frame.size.height+20.0, PageControlImg.size.width, PageControlImg.size.height)];
        [PageControlBtn setBackgroundImage:[UIImage imageNamed: (i==0)?@"page_selected.png":@"page_deselected.png"] forState:UIControlStateNormal];
        [PageControlBtn setBackgroundImage:[UIImage imageNamed: @"page_selected.png"] forState:UIControlStateHighlighted];
        [PageControlBtn setBackgroundImage:[UIImage imageNamed: @"page_deselected.png"] forState:UIControlStateDisabled];
        [PageControlBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
        [PageControlBtn setTitleColor:(i==0)?[UIColor whiteColor]:[UIColor grayColor] forState:UIControlStateNormal];
        PageControlBtn.titleLabel.font = [UIFont boldSystemFontOfSize:16.0];
        PageControlBtn.titleLabel.textAlignment = UITextAlignmentCenter;
        [PageControlBtn setTitle:[NSString stringWithFormat:@"%d", i+1] forState:UIControlStateNormal];
        [PageControlBtn setTitle:[NSString stringWithFormat:@"%d", i+1] forState:UIControlStateSelected];
        [PageControlBtn setTag:i+2000];
        [PageControlBtn addTarget:self action:@selector(onPageControlBtnPressed:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:PageControlBtn];
        [PageControlBtns addObject:PageControlBtn];
    scrollView.contentSize = CGSizeMake(scrollView.frame.size.width*PAGES_NUMBER, scrollView.frame.size.height);

Now, when any button is pressed, we need to do two things - manually scroll UIScrollView to appropriate page and change button look (both background image and text color), which is going to be implemented in a separate function:

    UIButton *button = (UIButton *)sender;
    int tag=button.tag-2000;
    [scrollView setContentOffset:CGPointMake(scrollView.frame.size.width*tag, 0) animated:YES];
    [self SetActivePageControlWithIndex:tag];

    for (int i=0; i<[PageControlBtns count]; i++)
        UIImage *backgroundImg = [[PageControlBtns objectAtIndex:i] backgroundImageForState:(i==index)?UIControlStateHighlighted:UIControlStateDisabled];
        [[PageControlBtns objectAtIndex:i] setBackgroundImage:backgroundImg forState:UIControlStateNormal];
        [[PageControlBtns objectAtIndex:i] setTitleColor:(i==index)?[UIColor whiteColor]:[UIColor grayColor] forState:UIControlStateNormal];

By now pressing a button would change scroll view's position, but it doesn't work vise-versa. So, we should change active page control when scroll view is scrolled. To do this, we will use UIScrollView's delegate method:

-(void)scrollViewDidEndDecelerating:(UIScrollView *)sender
    CGFloat pageWidth = scrollView.frame.size.width;
    int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
    [self SetActivePageControlWithIndex:page];

That's it - now you get response both ways - when scroll view is scrolled and when page control button is pressed. You can download source codes here.

May 1, 2013

Customizing UIToolbar

1. Changing background of UIToolbar

a) In case you just need to change background color, use tintColor property, for example:

toolbar.tintColor = [UIColor redColor];

b) In case your app runs iOS 5 and up only, use setBackgroundImage:forToolbarPosition:barMetrics: method, for example:

[toolbar setBackgroundImage:[UIImage imageNamed:@"toolbar.png"] forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];

c) In case you do need older iOS versions compatibility, you need to subclass UIToolbar and combine it with solution (b) using respondsToSelector: method. Basically you need to use setBackgroundImage:forState:barMetrics: method for iOS 5 and up, and override drawRect: method for older iOS versions:


#import <UIKit/UIKit.h>

@interface CustomToolbar : UIToolbar



#import "CustomToolbar.h"

@implementation CustomToolbar

- (id)initWithFrame:(CGRect)frame
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    return self;

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
    UIImage *image = [UIImage imageNamed: @"toolbar.png"];
    [image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];


Depending on your background picture selection, toolbar buttons might not look good. Here's and example:

The easiest fix would be to combine custom background with tintColor property (in this case it would be dark blue color):

toolbar.tintColor = [UIColor colorWithRed:0.0 green:0.11 blue:0.15 alpha:1.0];

Looks much better now:

If this is not enough, you might consider adding custom buttons to UIToolbar:

2. Adding custom buttons to UIToolbar

Create UIButton using any image that you want, and then create UIBarButtonItem using initWithCustomView: method. In case you want to have white glow when user touches the button similar to system bar button items, set showsTouchWhenHighlighted property to YES.

Here's some sample code:

UIButton *EmailBtn = [[UIButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 44.0, 44.0)];
[EmailBtn setBackgroundImage:[UIImage imageNamed:@"email.png"] forState:UIControlStateNormal];
EmailBtn.showsTouchWhenHighlighted = YES;

UIBarButtonItem *EmailBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:EmailBtn];
NSArray *buttons = [NSArray arrayWithObjects: EmailBarButtonItem, nil];
[toolbar setItems: buttons animated:NO];

And here's the final look:


Using the combination of custom UIToolbar background and custom bar button items gives you great possibilities for creating your own unique user experience. Enjoy!