Page Control: Display view controllers as data pages

Updated on July 6th, 2020

⏱ Reading Time: 8 mins

The Page Control, is a useful tool that allows to display data on an iPhone app in the form of pages. To get the idea, just thing of the main screen of the iPhone, where you navigate among pages to the left and right to display all the apps existing on the device.

To be more accurate, the main idea when you use the Page Control in an app is that you create and use as many instances of UIViewControllers that will display data as the pages you want your app to have. Usually, you need to display various data on the same UIViewController, so it’s common to create instances only of one UIViewController. This is what we’ll do here, we’ll create five instances of the same UIViewController to display all data we need.

Another thing that should be cleared is the following: The view controller that uses the Page Control and the view controller that displays the data is not the same. That means that you need a view controller to set up the Page Control and implement all the navigational work and another (or more) view controller that will be used in combination with the first one to display your data. Be sure to make that clear to yourself if you want to get out of troubles and make your life easier.

Let’s get a little deeper before starting implementation. The whole idea is to use just two controls in the view controller where the Page Control will reside: A UIScrollView that will be used to display our pages and it will be responsible for navigating among them and of course the Page Control itself. As long as it concerns the data display, we already said that you need a new (or more) view controller.

A final note before proceeding. I found various examples and tutorials about that topic in many documentations and here I present a simple approach of my own. What I’ ll show here is not the only way that can be used to implement the page functionality in an app, there are more ways for sure, but this is my way and in my opinion it’s a fast implementation that totally serves our needs.

The sample app here is quite simple but it fully demonstrates the use of pages. The app will use the Page Control to display five (5) pages (all instances of the same view controller). Each page will have a different background color and a message that will show the number of the current page. Our target is to manage to navigate through the pages either by sliding left or right, or by tapping on the Page Control to move to the next or to the previous page.

Let’s get started. In Xcode (I use version 4.2) create a new Single View Application and name it PageControlTestApp (or whatever you wish). After your project is ready, add the view controller that will display the data in our pages.


Step 1

Go to File–>New–>New File… and select the UIViewController subclass. Click Next and name the new view controller TestViewController (you may name it whatever you like, I’ll use that name). When the new view controller gets ready, open the TestViewController.xib file in the Interface Builder. In here, add a label (make it wide enough). In this label we’re going to show the index of each page.


Step 2

We need to create a couple of outlets in the TestViewController.h file. Add the following lines:

[objc]
@interface TestViewController : UIViewController
@property (retain, nonatomic) IBOutlet UIView *testView;
@property (retain, nonatomic) IBOutlet UILabel *lblMsg;
[/objc]

  • The testView outlet is the view of the TestViewController.
  • The lblMsg is the label we created in the previous step.

Go to the IB and connect these outlets to the view and the label equivalently. Don’t miss this step, you won’t see anything happening.

Don’t forget also to synthesize them in the .m file:

[objc]
@synthesize testView;
@synthesize lblMsg;
[/objc]

Not finished yet. Release:

[objc]
– (void)dealloc {
[testView release];
[lblMsg release];
[super dealloc];
}
[/objc]


Step 3

The TestViewController will do the following: It will be provided with the index of the current page and depending on that value it will set a specific background color and display a custom message with page’s index. Therefore, a int value for the current page is necessary:

[objc]
@property (nonatomic) int currentPage;
[/objc]

Synthesize it in the TestViewController.m file:

[objc]
@synthesize currentPage;
[/objc]

We also need a method that will be called to setup the view. So add the next method in the TestViewController.h file:

[objc]
-(void)updateView;
[/objc]

In the TestViewController.m file implement it. The comments are quite explanatory:

[objc]
-(void)updateView{
// Depending on the currentPage value the message that will display the page number
// and the background color of the view will be set in here.

// Declare a UIColor object that will keep the backgroundcolor of the view depending on the currentPage value.
UIColor *bgClr;

// Check the currentPage value.
switch (currentPage) {
case 0:
bgClr = [UIColor whiteColor];
break;
case 1:
bgClr = [UIColor redColor];
break;
case 2:
bgClr = [UIColor yellowColor];
break;
case 3:
bgClr = [UIColor greenColor];
break;
case 4:
bgClr = [UIColor orangeColor];
break;
default:
break;
}

// Set the background color of the view.
[self.view setBackgroundColor:bgClr];

// Set the message of the label.
NSString *msg = [NSString stringWithFormat:@"This is page #%d", (currentPage + 1)];

// Update the label’s message.
[lblMsg setText:msg];
}
[/objc]

That’s all about the TestViewController, the view controller that will display our data. Let’s go now to the real job.
As I said at the beginning of the post, we’ll create five instances of the TestViewController to present data in our pages. Before going any further though, let’s setup our view in the Interface Builder.


Step 4

Open the ViewController.xib in the IB and add the following controls:

    • UIScrollView (X:0, Y:0, Width: 320, Height: 416)
    • Page Control (under the UIScrollView). Make sure to make the Page Control wide enough if you want to make easy taps (or clicks in the Simulator). I have this frame: X: 0, Y: 424. Width: 320, Height: 36

.


Step 5

Create the outlets for the controls we added in the previous step inside the ViewController.h file:

[objc]
@interface ViewController : UIViewController ;
@property (retain, nonatomic) IBOutlet UIScrollView *scroll;
@property (retain, nonatomic) IBOutlet UIPageControl *pager;
[/objc]

Remember to connect them to the controls in the Interface Builder.

We also need:

  1. An array that will keep the view controllers that will be displayed in the pages.
  2. A value that will keep the number of the total pages.

In the previous code add the next two lines:

[objc]
@property (retain, nonatomic) NSMutableArray *viewControllers;
@property (nonatomic) int totalPages;
[/objc]

Now synthesize all of them in the ViewController.m file:

[objc]
@synthesize scroll;
@synthesize pager;
@synthesize viewControllers;
@synthesize totalPages;
[/objc]

Of course, now it’s the best time to release them:

[objc]
– (void)dealloc {
[scroll release];
[pager release];
[viewControllers release];
[super dealloc];
}
[/objc]


Step 6

Two things need to be done:

  1. Create a method that will initialize our objects.
  2. Create a method that will init and load an instance of the TestViewController for each of our pages (Step 4).

Here is the method that initializes the objects named (what else) initialize:

[objc]
-(void)initialize{
// For the sake of the example, let’s say that we want to have five pages.
totalPages = 5;

// Init the array of the view controllers that are going to be our pages.
viewControllers = [[NSMutableArray alloc] initWithCapacity:totalPages];

// So far so good.
// Let’s init the scroll view and set the necessary properties.
// You may play around with these properties.
[scroll setPagingEnabled:YES];
[scroll setShowsHorizontalScrollIndicator:NO];
[scroll setShowsVerticalScrollIndicator:NO];
[scroll setScrollsToTop:NO];
[scroll setDelegate:self];

// Watch out the content size of the scroll view. It’s important to understand that
// we want the scroll view to have the width of the screen (320.0 px in this example, only portrait orientation)
// multiplied by the total number of pages that will exist.
// This is how all of the view controllers are going to fit in the screen and make scrolling possible.
// The height is going to remain the same as we set it in the Interface Builder.
[scroll setContentSize:CGSizeMake(scroll.frame.size.width * totalPages, scroll.frame.size.height)];

// It’s time to init the page controller.
// Tell the page controller how many pages are going to be.
[pager setNumberOfPages:totalPages];
// Also tell it what is the default and current page.
[pager setCurrentPage:0];

// Enough initializing.
// Let’s load the pages for the first time.
for (int i=0; i [self loadPageWithIndex:i];
}
}
[/objc]

As you can see, inside the for loop the loadPageWithIndex: method is called. It will be implemented in the next step.

For now, go to the viewDidLoad method and call our method. In this way, our objects will be initialized every time the view loads.

[objc]
– (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

[self initialize];
}
[/objc]


Step 7

Now we’ll implement the loadPageWithIndex: method. This method will create an instance of the TestViewController every time that is called, it will provide the page index to that instance (remember the currentPage member variable in the TestViewController?) and it will force it to make all the necessary updates/changes using the updateView method of the TestViewController. After that, the view controller will be stored into the viewControllers array.
Here is the code:

[objc]
-(void)loadPageWithIndex:(int)page{
// Check the index of the page and make sure that is greater than zero and lower than the totalPages value.
// This check is necessary to avoid errors even by mistake.
if (page < 0 || page >= totalPages) {
// In this case just return.
return;
}
else{
// The page index has a valid value.
// The TestViewController is going to be the view controller for the pages of this example.
// Init an instance.
TestViewController *testView = [[TestViewController alloc] initWithNibName:@"TestViewController" bundle:nil];

// The TestViewController view will display only a message with the number of the current page
// and a different background in each page. So, set the page number to the testView object.
// Of course, in a real app, this is the place where you’ll provide any necessary data to the
// view controller that will be displayed and not the number of the current page.
// This is only for the example purposes.
[testView setCurrentPage:page];
// Update the view depending on the currentPage value.
[testView updateView];

// One more thing. The frame of the view controller must be set.
CGRect viewFrame = CGRectMake(scroll.frame.size.width * page, 0.0, scroll.frame.size.width, scroll.frame.size.height);
[testView.view setFrame:viewFrame];

// Add the view controller’s view to the scroll view.
[scroll addSubview:testView.view];

// Add the testView view controller into the viewControllers array.
[viewControllers addObject:testView];

// The testView is no longer needed.
[testView release];
}
}
[/objc]


Step 8

So far, every time our app loads our objects are initialized and the view controllers that display data in our pages are loaded as well. If you run the project right now in the Simulator you’ll see the first page with the specified background color and the appropriate message in the label, you may navigate to the next pages, but the Page Controller will not get updated.

What we need to do is to implement scrollViewDidScroll: delegate method (and that’s why we declared the UIScrollViewDelegate at Step 2). In every scroll/sliding/page transition we’ll figure out the current page index and we’ll update the Page Controller.

Here is the implementation:

[objc]
// In every scroll (page transition) the page control needs to be updated
// to indicate the correct page displayed.
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
// Calculating the page index. Not hard to understand it.
int page = floor(scrollView.contentOffset.x / [UIScreen mainScreen].bounds.size.width);

// Set the page index as the current page to the page control.
[pager setCurrentPage:page];
}
[/objc]

If you try now to move from page to page in the Simulator you’ll notice that the Page Control is indicating the current page.

Nice, but is our Page Control working when we tap/click on it?


Step 9

We need to create an IBAction that will be triggered every time the user taps on the Page Control. So, go to the ViewController.h file and declare this:

[objc]
– (IBAction)changePage;
[/objc]

Go to the Interface Builder and connect the action to the Page Control.

Now go to the ViewController.m file and implement it:

[objc]
// This action occurs when the user taps on the page control to change page.
– (IBAction)changePage {
// Get the index of the page.
int pageIndex = [pager currentPage];

// We need to move the scroll to the correct page.
// Get the scroll’s frame.
CGRect newFrame = [scroll frame];

// Calculate the x-coordinate of the frame where the scroll should go to.
newFrame.origin.x = newFrame.size.width * pageIndex;

// Scroll the frame we specified above.
[scroll scrollRectToVisible:newFrame animated:YES];
}
[/objc]

That’s all. Now you have a fully working demo app that displays data pages using the Page Control.
I hope it becomes useful.

Here are the screenshots of the final result:


Download the project here.

This tutorial was transferred as it was originally written in my blog, therefore some techniques, tools or SDKs may have changed since then.

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.