2011年10月16日星期日

How To Make An Interface With Horizontal Tables Like The Pulse News App: Part 1

How To Make An Interface With Horizontal Tables Like The Pulse News App: Part 1:
This is a blog post by iOS Tutorial Team member Felipe Laso, an independent iOS developer and aspiring game designer/programmer.

Tables... with Tables inside!
Tables... with Tables inside!

In this 2-part series you’ll learn how to make an interface similar to the Pulse News app for iPhone and iPad by using only UITableViews. Here are some of the things we’ll focus on for this series:

  • How to create a universal navigation-based app from the ground up
  • How to rotate a UITableView to make it scroll horizontally
  • How to insert a UITableView within a UITableViewCell
  • How to create custom UITableViewCells programmatically

Even if you don’t plan on making an app like Pulse news, you might like this tutorial as a great way to learn some UIKit tricks you might not have tried before.

This tutorial assumes you are familiar with the basics of UIKit development. If you are new to UIKit development, you might want to try the How To Create a Simple iPhone App tutorial series first.

You’ll be writing the entire application from scratch in this series, and you will be surprised at how little code it takes to create this cool custom interface!



Getting Started


Open up Xcode and go to File\New\New Project, from the list on the left select Application under iOS and you’ll now see a few icons with several template projects specifically tailored for iOS. We are going to use a Window-based Application so go ahead and select it and click the Next button.

Creating a new Windows-based app in Xcode

You now have a new window with options for your new project:

Setting project name and options in Xcode

Set the following options (as shown above):

  • Product Name: Enter HorizontalTables.
  • Company Identifier: If you have an iOS Developer Account you can test your apps on a device, so write the company identifier according to a developer provisioning profile you have setup on the iOS Developer Portal. For more info on this step, check out the iOS Code Signing Under The Hood tutorial on this site.
  • Device Family: Select Universal.
  • Use Core Data and Include Unit Tests: Uncheck these options because we will not be using them.

Go ahead and click the Next button and select where you would like to save your project. If you’d like to, you can click the checkbox to create a Git repository for this project, but since that’s beyond the scope of the tutorial I will not be using this option.

When you’re done, click Create, and you have a new empty project!

Settings & Cleanup


Before we write any code it would be wise to configure our project’s interface orientations and organize our groups and folders within Xcode. In the Project Navigator select the HorizontalTables project and on the right side select the HorizontalTables target.

You will see the Company Identifier you used when the project was created as well as the build and version numbers, the Devices support and Deployment target (which in my case is set to 4.3 but if you want to support previous iOS versions go ahead and change this to an earlier version).

What we are interested in is changing the supported interface orientations, for this tutorial we will support only the Portrait orientation for iPhones and iPods and the Portrait and Upside Down orientations on iPad so go ahead and make sure only those are selected.

Setting supported interface orientations in Xcode

Now I’m going to create a couple of folders (called groups) within our Xcode project so we can organize our files and resources a bit better. In the Project Navigator right click on the iPhone folder and select New Group, name it NIBs. Then create a second new folder under iPhone and name it Images.

Now drag the MainWindow_iPhone.xib file under the NIBs folder in iPhone.

Repeat these steps and create the same folders under the iPad folder. This is what the Project Navigator should look like when you’re done:

Organizing files into groups in Xcode

Adding a Navigation Controller


In order to add navigation our app we need a UINavigationController, let’s go ahead an add one.

Select the MainWindow_iPhone.xib interface file from our Project Navigator and you should see an iPhone window with a white background and a label that says “My Universal App On iPhone”, let’s delete the label as we will not be needing it.

Open up the Object Library by going to View\Utilities\Show Object Library (or with the keyboard shortcut Control + Option + Command + 3) and drag a UINavigationController object over to the Interface Dock. Click the small button at the bottom with the image of a small triangle pointing to the left to expand the Interface Dock.

Expanding the dock in Interface Builder

You should now have your Interface Dock looking like this:

Navigation Controller added to Interface Builder dock

Every UIWindow object has a property name rootViewController of type UIViewController, we are going to make our Navigation Controller be the window’s root view controller (since this is the first view we want to see when we launch our application).

In the Interface Dock control-click on the Window object and drag from the rootViewController outlet to the Navigation Controller object in the Interface Dock

Connecting the Navigation Controller to the rootViewController outlet in Interface Builder

The last thing we need to do is to write a property to our Navigation Controller within our App Delegate so that we may communicate with it at a later point.

In your Project Navigator open the HorizontalTablesAppDelegate.h header file (the normal one, not the _iPhone or _iPad subclasses) and add an IBOutlet property for the UINavigationController. Your App Delegate header should now look like this:


#import <UIKit/UIKit.h>

@interface HorizontalTablesAppDelegate : NSObject <UIApplicationDelegate>

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;

@end


And now in the implementation we have to synthesize our property and properly release it’s memory in the dealloc method. Your HorizontalTablesAppDelegate.m file should have the following changes:


@implementation HorizontalTablesAppDelegate

@synthesize window = _window;
@synthesize navigationController = _navigationController;

- (void)dealloc
{
[_window release];   
self.navigationController = nil;   
[super dealloc];
}


All that’s left to do is connect our navigationController property in our interface file. Select the MainWindow_iPhone.xib file from the Project Navigator and within the Interface Dock control-click on the App Delegate object and drag the navigationController outlet to our UINavigationController object.

Connecting Navigation Controller to outlet on App Delegate in Interface Builder

Go ahead and run the project on your device or the iPhone Simulator, this it what it will look like:

First version of app built for iPhone

In order to make our app universal we just have to repeat the same steps for the iPad Interface. We don’t have to rewrite our property or synthesize it because both the iPhone and iPad specific App Delegates inherit from the HorizontalTablesAppDelegate.h which already declares it.

In your MainWindow_iPad.xib file delete the label just like we did on the iPhone interface, drag a UINavigationController from the Object Library over to the Interface Dock and finally connect the Window’s rootViewController outlet and the App Delegate’s navigationController outlet to the Navigation Controller we just dragged over.

Understanding Our Interface


Before moving on, I wanted to give an overview of the interface we’re about to build.

The reason why we created a navigation-based project from a window-based application is that the navigation-based template is only designed for iPhone, so we would would have to make our app universal and manually add the interface and code files for iPad. The way we did it, we have a universal navigation-based app with very little work.

Plus, it’s fun practice to see how everything fits together from scratch! :]

Now let’s take a look at the interface we’re going to build:

Diagram of interface layout like Pulse News App with horizontal table views

The first thing we have is a regular UITableView that scrolls vertically. This will be the main element of our Navigation Controller. We will be adding a custom UITableViewController in a little bit in order to get this working.

Each cell inside this vertical table will contain a subclass of UITableViewCell, named HorizontalTableCell. This custom cell will contain a UITableView as a subview, but rotated 90 degrees counter clockwise so it scrolls horizontally.

We will then create another custom UITableViewCell named ArticleCell, rotate it (90 degrees clockwise) to get it face up again, and use it as the cells for the rotated table view within HorizontalTableCell.

So a summary of the steps required for things to work:

  1. Create a regular UITableView
  2. Create a custom UITableViewCell
  3. Add a rotated UITableView as a subview of our UITableViewCell
  4. Create another custom UITableViewCell for our articles
  5. Rotate our custom cell and use it for our horizontal table view

If this sounds complicated don’t worry, you’ll see how easy it is to make all of this work and how little code is actually required.

The Vertical Table


Let’s go ahead an implement our vertical table. The first thing we’ll need to do is create a custom subclass of UITableViewController. In our Project Navigator, right-click on the HorizontalTables folder and select New File. On the left side under the iOS file templates select Cocoa Touch\UIViewController subclass and click Next.

Creating a UIViewController subclass in Xcode

You will now see a screen where you can select a subclass from a drop down menu or write the class yourself, as well as some options regarding the file. For our subclass select UITableViewController, make sure that “Targeted for iPad” and “With XIB for user interface” are not selected, and click Next.

Creating a UITableViewController subclass, without a XIB

Type in ArticleListViewController for the file name and click on save. Your project should now have the ArticleListViewController.h and ArticleListViewController.m files

Xcode groups and files tree with new ArticleListViewController class

For now we won’t add anything to our ArticleListViewController.h file, but just to be tidy we are going to cleanup the ArticleListViewController.m file a bit. We will not be needing the following methods so go ahead and delete them:

  • initWithStyle
  • viewWillAppear
  • viewDidAppear
  • viewWillDisappear
  • viewDidDisappear
  • shouldAutorotateToInterfaceOrientation
  • didSelectRowAtIndexPath
  • cellForRowAtIndexPath
  • All the methods that are commented out

Wait a second, how are we going to provide cells for our rows if we are deleting the cellForRowAtIndexPath method?!? That’s because we are going to create two subclasses of ArticleListViewController, one for the iPhone interface and one for the iPad interface. That way we can control things like custom headers, row height and items specific to the interface of each device.

We delete the other methods because we are not going to be adding, deleting or modifying rows to the vertical table view, and we will not be selecting it’s cells. If you remember the graphic we just saw explaining the structure of the interface, each cell in the vertical table will have a table view within it, so we will be selecting the cells of those inner tables, not this outer one.

Before we create subclasses for iPhone and iPad make these changes in the ArticleListViewController.m file so we have some test rows to show:


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 10;
}


In order to keep our files, controllers, classes and resources organized within our project, in the Project Navigator control-click on the HorizontalTables folder and select Create New Group, name it View Controllers and put the ArticleListViewController .m and .h files in it.

Repeat the same process under the iPhone and iPad folder, as we are just about to create subclasses of ArticleListViewController.

This is what your project navigator will look like afterwards:

Groups created for View Controllers in Xcode

Subclassing Article List View Controller


Control-click your newly created View Controllers folder in the iPhone folder and select New File. Select Cocoa Touch from the list on the left side and UIViewController Subclass as the file type, go ahead and click Next.

For the subclass write in ArticleListViewController and make sure that With XIB for user interface is selected, click the Next button, name your file ArticleListViewController_iPhone, and click Save.

Creating the subclass of ArticleListViewController for the iPhone

Just to be tidy go ahead and drag the ArticleListViewController_iPhone.xib NIB file under the NIBs folder within the iPhone folder. This is what my Project Navigator looks like:

iPhone ArticleListViewController subclass XIB sorted into folder

Open ArticleListViewController_iPhone.h and add the following import to the top of the file:


#import "ArticleListViewController.h"


Then open up the ArticleListViewController_iPhone.m file and delete the initWithNibName method as well as the shouldAutorotateToInterfaceOrientation method. We’ll also add the cellForRowAtIndexPath method now so insert the following code into your implementation:


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}

cell.textLabel.text = @"Vertical Table Rows on iPhone";

return cell;
}


We’re almost ready to see our project running on the simulator but before we do that we must make a few small changes to the interface files. Open up the ArticleListViewController_iPhone.xib file.

In the Interface Dock you should have a View object, delete it and drag a Table View from the Object Library. We must connect the Table View’s view to our ArticleListViewController_iPhone class, control-click on File’s Owner and drag from the View outlet to Table View.

Connecting table view to view outlet on File's Owner

If you run your project in the iPhone simulator (or a device) you should see this:

Our second version of the iPhone article list - still empty!

Don’t worry, that’s expected as we haven’t finished wiring up our interface. Jump over to the MainWindow_iPhone.xib file for a second and open up the Interface Dock. Expand the Navigation Controller and select the Root View Controller.

Setting the root view controller of a UINavigationController in Interface Builder

In the Menu Bar go to View\Utilities\Show Identity Inspector (or use the Alt + Command + 3 shortcut). Making sure the Root View Controller is selected, change the class to ArticleListViewController_iPhone.

Setting the class of an object in Interface Builder

There’s one last thing we need to do to make things work. Still under the Utilities Pane, switch over to the Attributes Inspector by going over to View\Utilities\Show Attributes Inspector (or use the Alt + Command + 4 shortcut). With the Root View Controller selected change the NIB Name to ArticleListViewController_iPhone.

Setting the NIB Name for a View Controller in Interface Builder

Go ahead and run the project, this is what you should see:

Finally - our vertical table view working!

Yay! Things are working just as we expected. Don’t worry about the “Root View Controller” title on the navigation bar, we are going to add a custom image to it later.

You might find this a lot of work, but if you scroll back you’ll see how little work we’ve done and how, with just a couple of lines of code, we are now well on our way to having a universal Navigation-Based Application.

As you gain practice and understanding of View Controllers and how the connect with their interface files, this will become a walk in the park and something you’ll be able to do in just a couple of minutes.

Great, moving on!

Repeating The Process For iPad


We have our vertical table view working properly on iPhone but not on iPad (you can run the project in the iPad simulator to see this).

Just to remember the steps we must follow in order for things to work on iPad:

  1. Create a subclass of ArticleListViewController named ArticleListViewController_iPad (make sure you create an XIB interface for this class and target it for iPad)
  2. Import “ArticleListViewController.h” at the top of “ArticleListViewController_iPad.h”
  3. Cleanup the methods we don’t need inside the ArticleListViewController_iPad.m file
  4. Add the cellForRowAtIndexPath method


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}

cell.textLabel.text = @"Vertical Table Rows on iPad";

return cell;
}


  1. Delete the View in the ArticleListViewController_iPad.xib NIB file
  2. Add a Table View to the NIB file
  3. Connect the File’s Owner View outlet to the Table View
  4. In the MainWindow_iPad.xib file select the Navigation Controller’s Root View Controller and change its class to ArticleListViewController_iPad in the Identity Inspector
  5. In the Attributes Inspector put ArticleListViewController_iPad as the NIB name for the Root View Controller

Go ahead and run the project in the iPad simulator (or an actual iPad device) and this is what you’ll get:

Vertical table on the iPad

Not very interesting, I know! Specially since we can’t select rows or have anything particularly interesting going on with the interface. These are crucial steps as they will serve as the foundation for our highly customized UI later on, getting them right is just as important as the rest of the project.

Loading Articles Into Our Application


Our project is well on it’s way to being a cool looking app, but what good will it be if we don’t have any images or articles to actually display?

We’ll be loading the information we need off a property list file that we store in our application bundle. If you would like to load an RSS feed then check out the How To Make A Simple RSS Reader iPhone App tutorial available on this site.

I have created a simple property list made up of dictionary as the root object which contains seven arrays with a key (we will use this key as the category title). Each one of these seven arrays contains ten dictionaries that store the title, category and image name for our articles.

Go ahead and download the resources for this project, which includes the property list as well as the images we’ll need.

By now you should be comfortable creating folders within Xcode so go ahead and make a new one called Resources. Drag the Articles.plist and Article Images folder into this Resources group, make sure that “Copy items into destination group’s folder (if needed)” is checked, and click Finish.

Now that we have the property list in our project let’s load up the info and put it in our table to verify it’s working.

Back in our ArticleListViewController.h file let’s add an NSDictionary ivar as well as a property for it. Modify ArticleListViewController.h to look like the following:


#import <UIKit/UIKit.h>

@interface ArticleListViewController : UITableViewController
{
NSDictionary *_articleDictionary;
}

@property (nonatomic, retain) NSDictionary *articleDictionary;

@end


Let’s go ahead and synthesize our property adding the following line just after the implementation and set the property to nil in the viewDidUnload method:


@synthesize articleDictionary = _articleDictionary;

- (void)viewDidUnload
{
[super viewDidUnload];

self.articleDictionary = nil;
}


Now what we want to do in our viewDidLoad method is load up our dictionary with the contents of the property list, this is actually very simple. Make the following change to your viewDidLoad method:


- (void)viewDidLoad
{
[super viewDidLoad];

self.articleDictionary = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Articles" ofType:@"plist"]];
}


Before running our project let’s display the info on the table view we have. Still within our ArticleListViewController.m file go ahead and change the numberOfSectionsInTableview method and the numberOfRowsInSection method as follows:


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [self.articleDictionary.allKeys count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:nil ascending:YES selector:@selector(localizedCompare:)];
NSArray* sortedCategories = [self.articleDictionary.allKeys sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];

NSString *categoryName = [sortedCategories objectAtIndex:section];

NSArray *currentCategory = [self.articleDictionary objectForKey:categoryName];

return [currentCategory count];
}


You might be wondering why we use a sort descriptor to sort the array of keys we have. That’s because if you take a look at the property list file, before each category name there is a number. Since this is an NSDictionary, the categories will load up in different orders and I want them to show up in a specific order, specially the headlines which should be the first category

All you are doing here is creating a sort descriptor which will sort the objects in ascending order (from 1-7). When we set the title for each section we are gonna have to do some work with strings in order to remove that first character.

Another workaround would be making our dictionary be contained within an array in the property list, that way each item is located in a specific position and loaded in the same order, for our case that would be a bit overkill as removing the number is a one line process.

Let’s now get the titles for our sections by adding the method below:


- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:nil ascending:YES selector:@selector(localizedCompare:)];
NSArray* sortedCategories = [self.articleDictionary.allKeys sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];

NSString *categoryName = [sortedCategories objectAtIndex:section];

return [categoryName substringFromIndex:1];
}


We follow the same process of sorting our keys and getting the current category title depending on the section we are in. Finally all we do before returning the string with the category name is to get a substring starting from the first index (the number is located at index 0, which is where our string indexes begin).

Go ahead and build and run the project either on the iPhone or iPad simulator and this is what you’ll get:

Table View now displaying Section Headers based on plist

It’s working! And it loads up in the order specified which is just what we wanted :D

But we are still using that placeholder text for our rows, let’s go ahead and fix that. In order to do this we are going to have to change the cellForRowAtIndexPath method in both our ArticleListViewController_iPhone.m and ArticleListViewController_iPad.m files.

Right now the changes we will make are identical, which seems kind of pointless, but later on when we have custom table cells for each device, having specific view controllers will come in handy.

Open up ArticleListViewController_iPhone.m and make these changes to the cellForRowAtIndexPath method:


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}

NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:nil ascending:YES selector:@selector(localizedCompare:)];
NSArray* sortedCategories = [self.articleDictionary.allKeys sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];

NSString *categoryName = [sortedCategories objectAtIndex:indexPath.section];

NSArray *currentCategory = [self.articleDictionary objectForKey:categoryName];

NSDictionary *currentArticle = [currentCategory objectAtIndex:indexPath.row];

cell.textLabel.text = [currentArticle objectForKey:@"Title"];
cell.imageView.image = [UIImage imageNamed:[currentArticle objectForKey:@"ImageName"]];

return cell;
}


The keys we are using in order to get the title and image name for our article are not random, open up the Articles.plist file and you will see that those are the keys we used when creating the dictionaries. You could have used whatever you liked, or perhaps create definitions for these keys so they are not hard coded into our method, but for now this will do as we do no intend to change the names.

Make these changes to the cellForRowAtIndexPath method in the ArticleListViewController_iPad.m file as well and go ahead and run your project on either device.

This is what the project looks like on both iPhone and iPad:

Articles loaded into table view on iPhone with image and text

Articles loaded into table view on iPad with image and text

Already it’s looking quite good, don’t you think?

Some Visual Tweaks


Before wrapping up this first part, let’s make a few visual adjustments so we make our app look nicer and more in line with the final design. First up is a custom image for our navigation bar. The height for the navigation bar both on iPhone and iPad is 44 pixels, and for the Retina Display it’s 88 pixels high.

I have created a couple of custom navigation bars for our app (which is now called Raze News), which you’ll find in the resources for this project you downloaded earlier in the NavBar folder.

Add them to your project in the corresponding folders (the navigation bars for iPhone under Images in the iPhone/Images folder and the iPad navigation bar in the iPad/Images folder).

With the navigation bar images in place, let’s go ahead and customize the toolbar. Open up the HorizontalTablesAppDelegate.m file and add this above the implementation of the delegate:


@implementation UINavigationBar (UINavigationBarCustomDraw)

- (void)drawRect:(CGRect)rect
{
[[UIImage imageNamed:@"NavBar.png"] drawInRect:rect];
self.topItem.titleView = [[[UIView alloc] init] autorelease];

self.tintColor = [UIColor colorWithRed:0.6745098 green:0.6745098 blue:0.6745098 alpha:1.0];
}

@end


What this is doing is creating a Category for the UINavigationBar class, this means that we can add methods or override methods without having to subclass UINavigationBar.

All we do here is create an image with the name of the standard image and draw it in the navigation bar’s rect (if the app is being used on a Retina enabled device it will load the “@2x” image, and if it’s being loaded on iPad it will load the image with the “~iPad” at the end of the file name).

We then set the title of the navigation bar to an empty UIView, that’s so we don’t have a title on top of our custom image. And finally we set the tint color to the same gray we use for the background, this way the buttons on our navigation bar will have the same tint.

Keep in mind that there is a much better way of doing this in iOS5, but we can’t discuss here yet due to the NDA. All I can say is stay tuned for Steve Baranski’s killer tutorial on the subject! :]

Well that was simple huh? And the best part is that it will work throughout our entire application, meaning modal views, the mail composer as well as sub-view controllers we create.

Go ahead and run your project, this is what it looks like now:

Custom navigation bar on the iPhone

We are extra happy! :D But things still look a bit off, let’s take care of that right now.

One trick i discovered while working on this app is that on the iPhone we can have the Status Bar (which displays connection strength, battery life, etc.) with a custom tint color, it makes the app look gorgeous. Unfortunately this does not work on the iPad.

In your Project Navigator go to the HorizontalTables-Info.plist file under the Supporting Files folder. Add a line at the end of the file and for the key value search for Status Bar Style. Once that is selected for the value select Transparent Black Style (alpha of 0.5). This is what my info.plist file looks like:

Customizing status bar style in Info.plist

But if you run the app it still has a black Status Bar, which isn’t what we want. Open up the MainWindow_iPhone.xib file and in the Interface Dock select the Window object, now in the attributes inspector change the background color to these RGB values:

  • Red: 0
  • Green: 207
  • Blue: 109

It’s not the same green as the title in our navigation bar, but because the Status Bar is black with an alpha of 0.5 it will look just like our title color.

Customizing status bar color by changing Window background color

Make these changes on the MainWindow_iPad.xib file as well, even if the status bar won’t show it our app will be future proof in case Apple changes the Status Bar style on iPad.

Run the app and check out the Status Bar color:

Customizing the status bar color on the iPhone

Niiiiiiiiice, it’s a very subtle effect that makes our app look even better.

We are triple happy now! :] :] :]

The final thing we are going to do is customize our table view’s section headers so they too match the look of our app. This is very simple since the UITableViewDataSource provides us with a way to establish the height for each header as well as give it a custom UIView.

Open up the ArticleListViewController_iPhone.m file and add the following code:


#define kHeadlineSectionHeight  26
#define kRegularSectionHeight   18

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return section == 0 ? kHeadlineSectionHeight : kRegularSectionHeight;
}


That’s just like an if-else statement wrapped up in a single line. As an interesting tip it’s the only ternary operator available in C. The values used here can be changed to whatever you like, I found these to look good on the app but feel free to customize those as well. We declared some defines because we used these values a few times whilst customizing our headers, if you decide to change the height it will be quite easy coming in and changing the values just once.

Jump over to the ArticleListViewController_iPad.m file and add the following code:


#define kHeadlineSectionHeight  34
#define kRegularSectionHeight   24

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return section == 0 ? kHeadlineSectionHeight : kRegularSectionHeight;
}


Again these are just values that look good on the iPad, they can be anything you like.

Now in order to customize the section header we have to add another method from the table view data source. Go over to the ArticleListViewController_iPhone.m file and add the following code:


- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *customSectionHeaderView;
UILabel *titleLabel;
UIFont *labelFont;

if (section == 0)
{
customSectionHeaderView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, kHeadlineSectionHeight)] autorelease];

titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, tableView.frame.size.width, kHeadlineSectionHeight)];
labelFont = [UIFont boldSystemFontOfSize:20];
}
else
{
customSectionHeaderView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, kRegularSectionHeight)] autorelease];

titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, tableView.frame.size.width, kRegularSectionHeight)];

labelFont = [UIFont boldSystemFontOfSize:13];
} 

customSectionHeaderView.backgroundColor = [UIColor colorWithRed:0 green:0.40784314 blue:0.21568627 alpha:0.95];

titleLabel.textAlignment = UITextAlignmentLeft;
[titleLabel setTextColor:[UIColor whiteColor]];
[titleLabel setBackgroundColor:[UIColor clearColor]];  
titleLabel.font = labelFont;

NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:nil ascending:YES selector:@selector(localizedCompare:)];
NSArray* sortedCategories = [self.articleDictionary.allKeys sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];

NSString *categoryName = [sortedCategories objectAtIndex:section];

titleLabel.text = [categoryName substringFromIndex:1];

[customSectionHeaderView addSubview:titleLabel];
[titleLabel release];

return customSectionHeaderView;
}


Don’t be scared, let’s step through each portion of code to see what it does. First we create a UIView, UILabel and UIFont for our custom header.


if (section == 0)
{
customSectionHeaderView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, kHeadlineSectionHeight)] autorelease];

titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, tableView.frame.size.width, kHeadlineSectionHeight)];
labelFont = [UIFont boldSystemFontOfSize:20];
}
else
{
customSectionHeaderView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, kRegularSectionHeight)] autorelease];

titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, tableView.frame.size.width, kRegularSectionHeight)];

labelFont = [UIFont boldSystemFontOfSize:13];
}


This section creates a header with a different height and font size depending on whether it’s the headlines section or just a regular section. We initialize our UIView and UILabel with the same width of the table view and with the height specified earlier in our defines, finally the font size is set according to the category.


customSectionHeaderView.backgroundColor = [UIColor colorWithRed:0 green:0.40784314 blue:0.21568627 alpha:0.95];

titleLabel.textAlignment = UITextAlignmentLeft;
[titleLabel setTextColor:[UIColor whiteColor]];
[titleLabel setBackgroundColor:[UIColor clearColor]];  
titleLabel.font = labelFont;


What that out of the way, we change the background color of our custom view and set some properties on the UILabel like the text alignment, text color, background color as well as the font to use.


NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:nil ascending:YES selector:@selector(localizedCompare:)];
NSArray* sortedCategories = [self.articleDictionary.allKeys sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];

NSString *categoryName = [sortedCategories objectAtIndex:section];

titleLabel.text = [categoryName substringFromIndex:1];

[customSectionHeaderView addSubview:titleLabel];
[titleLabel release];

return customSectionHeaderView;


And last but not least we just get the current category title for the section just like we did earlier on the titleForHeaderInSectionMethod. Speaking of which, we don’t need that method anymore so jump over to the ArticleListViewController.m file and delete that method which we wrote earlier.

Go ahead and paste the following code in the ArticleListViewController_iPad.m file:


- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *customSectionHeaderView;
UILabel *titleLabel;
UIFont *labelFont;

if (section == 0)
{
customSectionHeaderView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, kHeadlineSectionHeight)] autorelease];

titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, tableView.frame.size.width, kHeadlineSectionHeight)];
labelFont = [UIFont boldSystemFontOfSize:26];
}
else
{
customSectionHeaderView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, kRegularSectionHeight)] autorelease];

titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, tableView.frame.size.width, kRegularSectionHeight)];

labelFont = [UIFont boldSystemFontOfSize:20];
} 

customSectionHeaderView.backgroundColor = [UIColor colorWithRed:0 green:0.40784314 blue:0.21568627 alpha:0.95];

titleLabel.textAlignment = UITextAlignmentLeft;
[titleLabel setTextColor:[UIColor whiteColor]];
[titleLabel setBackgroundColor:[UIColor clearColor]];  
titleLabel.font = labelFont;

NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:nil ascending:YES selector:@selector(localizedCompare:)];
NSArray* sortedCategories = [self.articleDictionary.allKeys sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];

NSString *categoryName = [sortedCategories objectAtIndex:section];

titleLabel.text = [categoryName substringFromIndex:1];

[customSectionHeaderView addSubview:titleLabel];
[titleLabel release];

return customSectionHeaderView;
}


This method is exactly the same as the iPhone method, the only change is the font size being used. This might seem silly right now, but this means the app is ready to have unique interfaces depending on each device.

Say you wanted to use images as backgrounds for the section header, perhaps different font types for each devices, colors, etc., this means it’s ready to be implemented and we avoid filling up our code with if statements for each device family.

We are being clean and making our code easier to change in the future. Should Apple launch an iPhone Nano in the future, all you have to do is write a custom subclass for this device, nothing is broken and no large if statements are put in your methods.

Go ahead and run your project, this is what it looks like:

Final project for this part of the tutorial!

Awesome… Now we are fully happy and joyful!!! :D

Where To Go From Here?


Here is an example project with all of the code from the above tutorial.

Included with the project for this first part are default images for our App as well as icons, so it looks extra pretty and polished.

Thank you very much for reading and be sure to check out Part 2 of the tutorial, that’s where the real magic happens. For now make sure you understand the concept shown here and feel free to tweak the colors, values and settings for the app.

If you have any questions or comments, please join the forum discussion below!



This is a blog post by iOS Tutorial Team member Felipe Laso, an independent iOS developer and aspiring game designer/programmer.

没有评论:

发表评论