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

Ray Wenderlich

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

Create this cool app with both vertical and horizontal table views!

Create this cool app with both vertical and horizontal table views!

This is the second of a two-part series on how to create horizontal table views in an interface similar to the Pulse News App.

If you haven’t already, check out part 1 of the tutorial that covers how to create the basic structure of the app.

We’ll continue with the example project where we left it off last time, so download it if you don’t have it already.

OK, let’s continue onwards!

Getting Started

If you remember, in the first part of this tutorial we left our interface with a custom navigation bar and section headers, but our table view still had the standard vertical rows.

In order for us to achieve the effect we are looking for we must create a subclass of UITableViewCell, let’s do that.

In your HorizontalTables project create a new group called Classes, for any custom classes and subclasses we create.

Classes Group created in Xcode

Now that we have that ready, let’s go ahead and get coding! First off we need to create our custom table view cell. We need to do this because by using Apple’s default cell styles, we will have cells with subtitles or pictures but not one that contains a table view embedded within.

Control-click on your newly created Classes folder and select New File, select Cocoa Touch on the left side list under iOS, choose the UIViewController Subclass file type and click Next.

On the next screen we are asked what we want to be a subclass of, our choice isn’t available from the drop down list but don’t worry, just type in UITableViewCell and make sure that both Targeted for iPad and With XIB For User Interface are unchecked, click Next again.

You are now prompted to enter a name for your class and save it, we will name it HorizontalTableCell so write it out and go ahead and Save.

Switch over to the HorizontalTableCell.m file, the implementation of our subclass, and you will see Xcode created a few methods for us, we will not be needing them so delete all of the methods within the file.

This is all that I have on my HorizontalTableCell.m file for now:

#import "HorizontalTableCell.h"
 
@implementation HorizontalTableCell
 
@end

Now let’s jump over to the HorizontalTableCell.h header file and add a few instance variables. This is the code you should have within the file:

#import <UIKit/UIKit.h>
 
@interface HorizontalTableCell : UITableViewCell <UITableViewDelegate, UITableViewDataSource>
{
    UITableView *_horizontalTableView;
    NSArray *_articles;
}
 
@property (nonatomic, retain) UITableView *horizontalTableView;
@property (nonatomic, retain) NSArray *articles;
 
@end

All we are doing is creating a UITableView, which we will rotate and add as a subview of our cell, and an NSArray to hold the articles for the category we are in. We are also making ourselves the table’s data source and delegate since we want each cell to manage its own content.

Go over to the HorizontalTableCell.m file and let’s synthesize our properties, create the appropriate dealloc method and add the necessary Table View Data Source and Delegate methods.

Add the following code in HorizontalTableCell.m:

#import "HorizontalTableCell.h"
 
@implementation HorizontalTableCell
 
@synthesize horizontalTableView = _horizontalTableView;
@synthesize articles = _articles;
 
#pragma mark - Table View Data Source
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{    
    return [self.articles count];
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellIdentifier = @"ArticleCell";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
 
    if (cell == nil) 
    {
       cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
    }
 
    cell.textLabel.text = @"The title of the cell in the table within the table :O";
 
    return cell;
}
 
#pragma mark - Memory Management
 
- (void)dealloc
{
    self.horizontalTableView = nil;
    self.articles = nil;
 
    [super dealloc];
}
 
@end

Let’s go over things bit by bit. First we synthesize our properties, nothing new here, and at the bottom we create the dealloc method so we can release those properties and avoid memory leaks.

Moving on to the table data source methods we have numberOfRowsInSection where we return the count of our articles array, and for the cellForRowAtIndexPath method we create a standard UITableViewCell for now. What we really want is a custom cell, but for testing purposes we are just going to use a standard cell.

Nothing new or complicated, right? All is good for now!

In order to add the horizontalTableView as a subview of our cell we must override the initWithFrame method, but before doing that we are going to create a utility file that will store all of the sizes and colors for our custom interface.

I found this to be very useful since we will need to share these values across multiple files and it will allow you to make changes to your custom interface just once within a single file.

Just to keep things nice and clean I’m going to create a new folder within my project navigator called Utility Files, you should know the drill by know so go ahead and right click on the HorizontalTables folder within your Project Navigator and select New Group.

Name it Utility Files and drag it over to where you like it best. We are only going to put our constants file here, but say you wanted to expand on this project and make it load RSS feeds (like Ray did on a previous tutorial found here: How To Make A Simple RSS Reader iPhone App Tutorial) then you can put the ASIHTTPRequest, GDataXML or utility files here.

This is what my Project Navigator looks like:

Xcode Groups and Files tree with Utility Files group

Now that we have our Utility Files folder created, right click and select New File. Choose C and C++ from the list to the left, select Header File and click Next. Name the file ControlVariables and Save it.

By default Xcode will create some pre-compiler defines for us but we will not be needing them so delete them from your file.

Going back to our HorizontalTableCell subclass you will remember I told you we had to override the initWithFrame method, well we could do it in the HorizontalTableCell.m file but what happens if you want to have different sized interfaces for iPhone and iPad?

Once again the solution is to create subclasses for both iPhone and iPad, so let’s go ahead and do just that!

In order to keep things tidy and organized just as we have so far, create a new folder under the iPhone folder and name it Classes, you should be well aware of how to do this by now so I won’t bore you with the details.

Now go ahead and right click on your newly created Classes folder and select New File, we are going to make the iPhone subclass of HorizontalTableCell. From the list select Cocoa Touch on the left and pick Objective-C subclass.

Click Next and on the next screen, when asked for the subclass, write HorizontalTableCell. Click Next once again and save the file as HorizontalTableCell_iPhone.

Leave the HorizontalTableCell_iPhone.h header file empty, we don’t need to add anything else to it. Jump over to the implementation file and delete the init method created for you (remember we are going to need initWithFrame).

Also make sure you add the following line at the top of your HorizontalTableCell.m file in order to import the control variables:

#import "ControlVariables.h"

Before writing initWithFrame, let’s jump over to our ControlVariables.h and actually add some of the values we will need for our custom interface. Add this to your ControlVariables.h file:

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// iPhone CONSTANTS
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
// Width (or length before rotation) of the table view embedded within another table view's row
#define kTableLength                                320
 
// Width of the cells of the embedded table view (after rotation, which means it controls the rowHeight property)
#define kCellWidth                                  106
// Height of the cells of the embedded table view (after rotation, which would be the table's width)
#define kCellHeight                                 106
 
// Padding for the Cell containing the article image and title
#define kArticleCellVerticalInnerPadding            3
#define kArticleCellHorizontalInnerPadding          3
 
// Padding for the title label in an article's cell
#define kArticleTitleLabelPadding                   4
 
// Vertical padding for the embedded table view within the row
#define kRowVerticalPadding                         0
// Horizontal padding for the embedded table view within the row
#define kRowHorizontalPadding                       0
 
// The background color of the vertical table view
#define kVerticalTableBackgroundColor               [UIColor colorWithRed:0.58823529 green:0.58823529 blue:0.58823529 alpha:1.0]
 
// Background color for the horizontal table view (the one embedded inside the rows of our vertical table)
#define kHorizontalTableBackgroundColor             [UIColor colorWithRed:0.6745098 green:0.6745098 blue:0.6745098 alpha:1.0]
 
// The background color on the horizontal table view for when we select a particular cell
#define kHorizontalTableSelectedBackgroundColor     [UIColor colorWithRed:0.0 green:0.59607843 blue:0.37254902 alpha:1.0]

I have included comments so it’s easy for you to remember what each constant does, that way it’s easier to make changes instead of having to change values and run your app before knowing what they do.

The values used here are just the ones I found to my liking after some testing, but you can of course change this and tweak them as you see fit!

Let’s go over them one at a time:

  • kTableLength: Since we are going to rotate our table view horizontally, we must manually set the length so that it fills the entire width of the screen. For now we use 320 which is the default iPhone size, but say you wanted to make it less than that in order to further customize your interface, just change this value. (By the way it will automatically resize to 640 pixels wide on devices with Retina Display)
  • kCellWidth and kCellHeight: Inside our horizontal tables we don’t want the default sized UITableView cells. First because we are gong to make them squared, and second because even though your table might be 100 pixels wide, the cells will be larger than that, wasting memory and resources drawing something that will not be seen
  • kArticleCellVerticalInnerPadding and kArticleCellHorizontalInnerPadding: These will enable us to make our the cells inside of our horizontal table just a tiny bit smaller, that way we have some separation between the pictures and titles of each article
  • kArticleTitleLabelPadding: This is just the amount of pixels we want the UILabel that will have the title of our articles to be separated from the left and right side of the cell. This will make the titles more readable
  • kRowVerticalPadding and kRowHorizontalPadding: These values are the amount of pixels we want to have between the horizontal table view and the cells the are a subview of
  • kVerticalTableBackgroundColor and kHorizontalTableBackgroundColor are the background colors of both the vertical table view and the horizontal table view respectively. This is just so that when the user scrolls past the available rows, or when the app loads, we can see backgrounds similar to our interface colors and not the regular white
  • kHorizontalTableSelectedBackgroundColor: This is just the color we want on our cell when the user selects a particular article. This is good so we don’t get the regular blue which will seriously break the design we are going for

These variables will become quite clear in a few minutes, you will see where each one of them goes and it will be easier to detect what they do. Now all we have to do when we want to change our interface (perhaps to reuse it for a different client or design) is change a few values here and voila!!!

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

- (NSString *) reuseIdentifier 
{
    return @"HorizontalCell";
}
 
- (id)initWithFrame:(CGRect)frame
{
    if ((self = [super initWithFrame:frame]))
    {
        self.horizontalTableView = [[[UITableView alloc] initWithFrame:CGRectMake(0, 0, kCellHeight, kTableLength)] autorelease];
        self.horizontalTableView.showsVerticalScrollIndicator = NO;
        self.horizontalTableView.showsHorizontalScrollIndicator = NO;
        self.horizontalTableView.transform = CGAffineTransformMakeRotation(-M_PI * 0.5);
        [self.horizontalTableView setFrame:CGRectMake(kRowHorizontalPadding * 0.5, kRowVerticalPadding * 0.5, kTableLength - kRowHorizontalPadding, kCellHeight)];
 
        self.horizontalTableView.rowHeight = kCellWidth;
        self.horizontalTableView.backgroundColor = kHorizontalTableBackgroundColor;
 
        self.horizontalTableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
        self.horizontalTableView.separatorColor = [UIColor clearColor];
 
        self.horizontalTableView.dataSource = self;
        self.horizontalTableView.delegate = self;
        [self addSubview:self.horizontalTableView];
    }
 
    return self;
}

Again, let’s go over each line in detail.

if ((self = [super initWithFrame:frame]))

First we begin by creating a reuse identifier for our cell, then we move on to initWithFrame starting with the usual call to our super class’ initWithFrame and assign it to self, a normal thing if you have been working with Objective-C for a while. The next thing we do is allocate a UITableView and store it in our local variable named horizontalTableView.

self.horizontalTableView = [[[UITableView alloc] initWithFrame:CGRectMake(0, 0, kCellHeight, kTableLength)] autorelease];

When creating the table view you will see we pass a new CGRect with four values. The first two (0, 0) mean that we want to position our table at the top left corner of the containing view, the second values are for the table’s width and height respectively. We also autorelease it

If you take a closer look you will see than we actually pass in the kCellHeight value for the width, and the table length for the height. That’s because right now we are creating our table horizontally, but when we rotate it the table’s width will actually become the vertical value (the height), and the length (meaning how long the table goes up and down when it’s vertical) will be the width.

self.horizontalTableView.showsVerticalScrollIndicator = NO;
        self.horizontalTableView.showsHorizontalScrollIndicator = NO;

After creating the table we turn off the vertical and horizontal scroll indicators, we don’t want those to appear as they will be on top of our beautiful custom cells.

 self.horizontalTableView.transform = CGAffineTransformMakeRotation(-M_PI * 0.5);

The next line is where things get interesting, for our horizontalTableView’s transform property, we make a CGAffineTransformMakeRotation, all this is doing is making a call to Core Graphic’s API in order to rotate our view 90 degrees counter clockwise. That’s what the -M_PI * 0.5 value is telling the API.

Believe it or not that little line is the entire magic of our interface, it’s what allows us to go from a traditional table view that scrolls up and down, to a beautiful custom interface like we see in the Pulse News App (and that’s now a part of our toolbox!)

One optimization trick is to use multiplication instead of division when possible, it’s better for iOS devices to perform multiplications rather than divisions, so when possible try to use that. Even though the performance advantages might not be noticeable, it will instill good habits for the future.

The rest is just values to make the table and cells bigger or smaller, but the crucial part is the rotation of the table.

[self.horizontalTableView setFrame:CGRectMake(kRowHorizontalPadding * 0.5, kRowVerticalPadding * 0.5, kTableLength - kRowHorizontalPadding, kCellHeight)];

After rotating our table we set the table view’s frame again. You might be asking yourself why we created a frame in the table’s initializer, well since we now rotated it we must reset the values of the table. And since a UITableView cannot be instantiated with a simple call to init, we must repeat this process.

In any case we are being clean and thorough, preventing any bugs or erroneous behavior.

self.horizontalTableView.rowHeight = kCellWidth;
        self.horizontalTableView.backgroundColor = kHorizontalTableBackgroundColor;

Next up we set the height of our table view’s rows, but since our table is now rotated and appears horizontally, the row height is actually the width of our cell now, which is why we use the kCellWidth constant. It’s just a game of words because Apple’s API refers to a table vertically but we are using it horizontally, nothing tricky here.

We also set the background color of the horizontalTableView to the constant we declared earlier.

self.horizontalTableView.dataSource = self;
        self.horizontalTableView.delegate = self;
        [self addSubview:self.horizontalTableView];

Finally, we set ourselves as the table’s data source and delegate, and add the horizontalTableView we just created and customized as a subview of the cell.

It might seem complicated right now, but take a few minutes to step away from the code, go grab a drink and come back, these are methods that many of you will be used to by now having used table views in your projects and apps. The only sort of “new” thing we learned is how to rotate the table, and just adding it as a subview of the cell.

We could go ahead and Run our project but we will see the exact same App we wrote in Part 1 of the tutorial, that’s because we have not done anything to reflect visually yet, we still have a little bit of customizing to do before changing things up.

One thing we can do right now is Analyze our project, the Xcode 4 includes the LLVM and LLDB which can do a static analysis of our code for possible memory leaks and errors.

In order to do this go to your Menu Bar and select Product\Analyze (or use the shortcut Shift + Command + B). If your code is bug free then you shouldn’t see any warnings or issues within Xcode, this is a good way to check your code while working (though it doesn’t replace proper testing, QA and profiling).

All is clean, YAY! :] We are very good programmers, so let’s keep on going.

Creating The Article Cells

Right now we have vertical table view, and we have just created a custom UITableViewCell that has a horizontal table inside of it. But in order for our interface to be really cool we need another custom cell to put inside our horizontal table, let’s to that right now.

Create a subclass of UITableViewCell and name it ArticleCell, put it inside the global Classes folder in your project navigator. I will also let you do this part on your own as we have covered creating new classes and subclasses extensively in both parts of the tutorial (small challenges are good on our goal to become awesome iOS developers).

Now add all of this code to your ArticleCell.h file:

#import <UIKit/UIKit.h>
 
@interface ArticleCell : UITableViewCell 
{
    UIImageView *_thumbnail;
    UILabel *_titleLabel;
}
 
@property (nonatomic, retain) UIImageView *thumbnail;
@property (nonatomic, retain) UILabel *titleLabel;
 
@end

We are creating a UIImageView to hold the thumbnail picture for our article and a UILabel that will have the title of our article. We also create the appropriate properties.

That’s it, now jump over to the ArticleCell.m file and delete all of the methods that Xcode added for us by default. Let’s now synthesize our properties, create the dealloc method and give our cell a reuse identifier, add the following code:

#import "ArticleCell.h"
 
@implementation ArticleCell
 
@synthesize thumbnail = _thumbnail;
@synthesize titleLabel = _titleLabel;
 
- (NSString *)reuseIdentifier 
{
    return @"ArticleCell";
}
 
- (void)dealloc
{
    self.thumbnail = nil;
    self.titleLabel = nil;
 
    [super dealloc];
}
 
@end

We synthesize our properties as usual, we also release them in the dealloc method as we should. The only interesting bit going on is the reuseIdentifier method we added.

As you have probably seen, when we create a new UITableViewCell we pass in a reuse identifier so that instead of creating a new instance of a cell for each one we have on a table, we simply reuse those that are currently not being displayed.

In our case because we are using a custom UITableViewCell, we must manually define the reuse identifier which I have named ArticleCell for our project. This is very good practice as it will optimize the scrolling performance of your tables!

That’s it, now we have to create a subclass of ArticleCell for iPhone and iPad in order to override the initWithFrame method and customize the look of our cell.

Create a subclass of ArticleCell and name it ArticleCell_iPhone, place it within the Classes folder inside the iPhone folder, just so we keep things tidy as we have so far.

Leave the ArticleCell_iPhone.h header file empty as it is and delete all of the methods created for us inside the ArticleCell_iPhone.m implementation file. Once that is done go ahead and paste this code inside the implementation file:

#import "ArticleCell_iPhone.h"
#import "ControlVariables.h"
 
@implementation ArticleCell_iPhone
 
- (id)initWithFrame:(CGRect)frame
{
    [super initWithFrame:frame];
 
    self.thumbnail = [[[UIImageView alloc] initWithFrame:CGRectMake(kArticleCellHorizontalInnerPadding, kArticleCellVerticalInnerPadding, kCellWidth - kArticleCellHorizontalInnerPadding * 2, kCellHeight - kArticleCellVerticalInnerPadding * 2)] autorelease];
    self.thumbnail.opaque = YES;
 
    [self.contentView addSubview:self.thumbnail];
 
    self.titleLabel = [[[UILabel alloc] initWithFrame:CGRectMake(0, self.thumbnail.frame.size.height * 0.632, self.thumbnail.frame.size.width, self.thumbnail.frame.size.height * 0.37)] autorelease];
    self.titleLabel.opaque = YES;
	self.titleLabel.backgroundColor = [UIColor colorWithRed:0 green:0.4745098 blue:0.29019808 alpha:0.9];
    self.titleLabel.textColor = [UIColor whiteColor];
    self.titleLabel.font = [UIFont boldSystemFontOfSize:11];
    self.titleLabel.numberOfLines = 2;
    [self.thumbnail addSubview:self.titleLabel];
 
    self.backgroundColor = [UIColor colorWithRed:0 green:0.40784314 blue:0.21568627 alpha:1.0];
    self.selectedBackgroundView = [[[UIView alloc] initWithFrame:self.thumbnail.frame] autorelease];
    self.selectedBackgroundView.backgroundColor = kHorizontalTableSelectedBackgroundColor;
 
    self.transform = CGAffineTransformMakeRotation(M_PI * 0.5);
 
    return self;
}
 
@end

First off we import the ControlVariables.h header file, that’s so we can read in our constants without getting warnings or errors.

Next up we override the initWithFrame method with our custom one, we do the usual call to our super class’ initWithFrame method and inside it we put our custom code, so let’s go over each part one by one:

self.thumbnail = [[[UIImageView alloc] initWithFrame:CGRectMake(kArticleCellHorizontalInnerPadding, kArticleCellVerticalInnerPadding, kCellWidth - kArticleCellHorizontalInnerPadding * 2, kCellHeight - kArticleCellVerticalInnerPadding * 2)] autorelease];
    self.thumbnail.opaque = YES;
 
[self.contentView addSubview:self.thumbnail];

We allocate and initialize a new UIImageView and give it a custom frame. As you will see we don’t position it at 0, 0 (the top left corner) but we give it a little bit of padding, as you remember a while back I told you this will allow for separation between each article cell.

Then we set the thumbnail as opaque, Apple recommends that all views are opaque when possible as it will greatly improve the performance of our UI.

After that we add the thumbnail as a subview of the cell’s content view, there’s a difference between adding it as a subview of the cell, and a subview of the cell’s content view!

self.titleLabel = [[[UILabel alloc] initWithFrame:CGRectMake(0, self.thumbnail.frame.size.height * 0.632, self.thumbnail.frame.size.width, self.thumbnail.frame.size.height * 0.37)] autorelease];
    self.titleLabel.opaque = YES;
    self.titleLabel.backgroundColor = [UIColor colorWithRed:0 green:0.4745098 blue:0.29019808 alpha:0.9];
    self.titleLabel.textColor = [UIColor whiteColor];
    self.titleLabel.font = [UIFont boldSystemFontOfSize:11];
    self.titleLabel.numberOfLines = 2;
    [self.thumbnail addSubview:self.titleLabel];

Moving on we create a UILabel and for the frame we make some adjustments so that it has some padding on the left side (thus preventing text to start from the very edge of the cell) and we make the height of the title label just 37% of the total height of the picture and we move it down a bit so it fits the bottom part of the cell.

This will make the title not cover the entire article cell and it’s image, but just the bottom half part. We then set the label as opaque, change the background color to a semitransparent gray, give it a white color for the text, set its font to the default bold font with a size of 11 points, allow the label to show in two lines and finally add it as a subview of the thumbnail.

Again, pretty standard stuff

self.backgroundColor = [UIColor colorWithRed:0 green:0.40784314 blue:0.21568627 alpha:1.0];
    self.selectedBackgroundView = [[[UIView alloc] initWithFrame:self.thumbnail.frame] autorelease];
    self.selectedBackgroundView.backgroundColor = kHorizontalTableSelectedBackgroundColor;

Up next we set the background color of the cell to something along the lines of our interface, we change the size of the cell’s background view to the same size as our thumbnail and give it the custom background color we added in our constants file earlier.

    self.transform = CGAffineTransformMakeRotation(M_PI * 0.5);

Finally we just rotate the cell 90 degrees clockwise by using M_PI * 0.5. If you find this a bit off then let’s remember a few things, these cells will be inside the rotated table view so if we just pass them in as they are, we will see them rotated 90 degrees counter clockwise.

In order for us to see them properly we have to rotate them back so they appear normal.

As you can see making the interface is basic stuff, we just have to keep good track of which table is the vertical one and which one is the rotated horizontal table, and of course we have to keep track of our rotations and how things will show up in our interface.

Testing At Last!

The time has finally come for us, dear readers, to make some tweaks and see our work so far :D

Go over to the HorizontalTableCell_iPhone.m implementation file and add the following import at the top of the file:

#import "ArticleCell_iPhone.h"

We are going to use our custom cell so we need the import. Now go ahead and add the following code:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ArticleCell";
 
    ArticleCell_iPhone *cell = (ArticleCell_iPhone *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
 
    if (cell == nil) 
    {
        cell = [[[ArticleCell_iPhone alloc] initWithFrame:CGRectMake(0, 0, kCellWidth, kCellHeight)] autorelease];
    }
 
	NSDictionary *currentArticle = [self.articles objectAtIndex:indexPath.row];
 
    cell.thumbnail.image = [UIImage imageNamed:[currentArticle objectForKey:@"ImageName"]];
    cell.titleLabel.text = [currentArticle objectForKey:@"Title"];
 
    return cell;
}

All we are doing here is creating a string for the reuse identifier of our article cells, notice we used the same reuse identifier as we did in the ArticleCell class.

After that we create a new ArticleCell_iPhone cell, we use the standard dequeueReusableCellWithIdentifier method provided to us by UITableView, except that it returns a regular UITableViewCell, so we cast it to our custom subclass.

If we don’t get returned a reusable cell we allocate and initialize one with a custom frame that positions it at the very top left corner of its container and makes it the same width and height as our cell.

And in order to set the image and title of our cell we just fetch the current article dictionary from our articles array and set the thumbnail image and article title accordingly.

An interesting thing to mention here is that we already wrote a cellForRowAtIndexPath method back in the ArticleCell class, so why do we do that over there if we just customized it for iPhone right now?

Well because we will get a warning back in ArticleCell.m about an incomplete UITableViewDataSource implementation, so we had to add that there.

Before going on we must change the number of rows in our ArticleListViewController.m file, right now we are returning the number of articles inside each category, we now need a single row because the horizontal table will handle all of the articles.

Go over to ArticleListViewController.m and change the numberOfRowsInSection method to the following:

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

Now go back to your ArticleListViewController_iPhone.m file and let’s change things a bit so we now use our custom HorizontalTableCell custom class. Add the following imports at the top:

#import "HorizontalTableCell_iPhone.h"
#import "ControlVariables.h"

Next up add the following method:

- (void)awakeFromNib
{
    [self.tableView setBackgroundColor:kVerticalTableBackgroundColor];
    self.tableView.rowHeight = kCellHeight + (kRowVerticalPadding * 0.5) + ((kRowVerticalPadding * 0.5) * 0.5);
}

This changes the background color of our vertical table and also changes the height of each row so that it can now fit our custom Article Cell.

Finally, replace the code in the cellForRowAtIndexPath method with this one:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{    
    static NSString *CellIdentifier = @"HorizontalCell";
 
    HorizontalTableCell_iPhone *cell = (HorizontalTableCell_iPhone *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
 
    if (cell == nil)
    {
        cell = [[[HorizontalTableCell_iPhone alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, tableView.frame.size.height)] 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];
 
    cell.articles = currentCategory;
 
    return cell;
}

This should look pretty familiar, we create a reuse identifier for our cell, try to dequeue one and if that’s not possible we create one. We then sort our categories just like we did in Part 1 of the Tutorial, we get the current category’s array of articles, and instead of fetching a single article, we pass the entire array to our HorizontalTableCell.

That should all that’s necessary for you to run your project now…Go ahead and build and run your project to see what it looks like so far!

This is what I got:

Horizontally scrolling table view

Yay! We have a table that scrolls vertically with horizontal cells embedded within each row that scroll horizontally….AWESOME!

^_^

But there are two things you might have noticed, one is that when we select an article the title label goes clear, and second, if you scroll up and down you might see that the images are repeating and not showing the category’s actual images.

Let’s fix these two things beginning with the label background.

Fixing The Label

The reason why the UILabel becomes transparent when selected has to do with it’s inner workings and how it automatically changes background colors. In order to prevent this we must subclass UILabel and override two very tiny methods for things to work properly.

It might be tedious having all these subclasses but as you have seen there is very little code inside of each one.

Create a new subclass of UILabel under your global Classes folder and name it ArticleTitleLabel. Add this code so your ArticleTitleLabel.h header file looks like this:

#import <UIKit/UIKit.h>
 
@interface ArticleTitleLabel : UILabel 
{
 
}
 
- (void)setPersistentBackgroundColor:(UIColor*)color;
 
@end

We declare a method that will set a persistent background color, that is all. Move on over to the ArticleTitleLabel.m file and delete all of the methods already on it, now add the following two methods and an import for our control variables header file:

// Add to top of file
#import "ControlVariables.h"
 
// Add new methods
- (void)setPersistentBackgroundColor:(UIColor*)color 
{
    super.backgroundColor = color;
}
 
- (void)setBackgroundColor:(UIColor *)color 
{
    // do nothing - background color never changes
}
 
- (void)drawTextInRect:(CGRect)rect
{    
    CGFloat newWidth = rect.size.width - kArticleTitleLabelPadding;
    CGFloat newHeight = rect.size.height; 
 
    CGRect newRect = CGRectMake(kArticleTitleLabelPadding * 0.5, 0, newWidth, newHeight);
 
    [super drawTextInRect:newRect];
}

When we call setPersistenBackgroundColor all we do is set the UILabel’s background color. Now when it tries calling it’s own setBackgroundColor method we will not do anything, thus preventing the color from changing when selecting our cell.

We override the drawTextInRect method so that the text inside the label can have a little bit of padding on the left and right side, we use the constants created in our ControlVariables.h file to determine the padding and make the label draw it’s text within the newly provided CGRect.

And that’s it, now let’s change the title label from an instance of UILabel to our subclass. Go over to ArticleCell.h and make the following changes:

#import <UIKit/UIKit.h>
 
// STEP 1
@class ArticleTitleLabel; 
 
@interface ArticleCell : UITableViewCell 
{
    UIImageView *_thumbnail;
 
	// STEP 2
    ArticleTitleLabel *_titleLabel;
}
 
@property (nonatomic, retain) UIImageView *thumbnail;
 
// STEP 3
@property (nonatomic, retain) ArticleTitleLabel *titleLabel;
 
@end

We add a forward declaration for our ArticleTitleLabel class in step 1, in step 2 we change the class of our titleLabel to ArticleTitleLabel and in step 3 we do the same but for our property.

Go to the ArticleCell_iPhone.m file now and make these changes:

  1. Add this import at the top of your file: #import “ArticleTitleLabel.h”
  2. Change the line where we create the titleLabel ([UILabel alloc] init]…) to the following: self.titleLabel = [[[ArticleTitleLabel alloc] initWithFrame:CGRectMake(0, self.thumbnail.frame.size.height * 0.632, self.thumbnail.frame.size.width, self.thumbnail.frame.size.height * 0.37)] autorelease];
  3. Change the line where we change the background color of our label (self.titleLabel.backgroundColor = …) to the following: [self.titleLabel setPersistentBackgroundColor:[UIColor colorWithRed:0 green:0.4745098 blue:0.29019808 alpha:0.9]];

And finally go to HorizontalTableCell_iPhone.m file and add the following import at the top:

#import "ArticleTitleLabel.h"

Go ahead and run your project again, now when you select the Article Cell the background will remain green! Awesome :D

Fixing The Duplicate Rows

This fix is not as obvious as the title label. Because of how the table view API works regarding reusable cells when we scroll our table and a cell is about to appear, we ask the system for a reusable cell from the cache and we replace it’s data accordingly.

When the user scrolls this process is invisible and you see different table cells as you scroll. But since we have a table view embedded within each row of our vertical table, we are refreshing the vertical cells but our horizontal table view doesn’t get refreshed.

One solution would be to call reloadData at the end of ArticleListViewController_iPhone.m’s cellForRowAtIndexPath method. The problem with this is that reload data reloads absolutely everything, meaning the cells that are visible as well as the cells that aren’t visible.

This method is useful when you change the data of the table, for example if we were loading our news from an RSS feed, when the user refreshes the articles and we download new data, we need to call the reloadData method.

Since we have several rows each with many images and custom content within, reloading everything within the table is overkill, and if you do reload the data you will notice stuttering and scrolling that isn’t smooth.

Not what Apple recommends and definitely not a good user experience!

The simplest solution is to create an array of horizontalTableCell cells that we can just create when we first load our app and then reuse those cells instead of reloading the entire data of the table.

Let’s just straight to it! Again this is not complicated at all.

Open the ArticleListViewController.h file and add the following instance variable and property to your class declaration:

@interface ArticleListViewController : UITableViewController
{
    NSDictionary *_articleDictionary;
    NSMutableArray *_reusableCells;
}
 
@property (nonatomic, retain) NSDictionary *articleDictionary;
@property (nonatomic, retain) NSMutableArray *reusableCells;
 
@end

We just created an NSMutableArray called reusableCells that will store our horizontalTableCell instances for reuse.

Moving over to the ArticleListViewController.m file we now have to synthesize the variable and properly release its memory:

// Right below the @implementation line
@synthesize reusableCells = _reusableCells;
 
// Inside dealloc and viewDidUnload
self.reusableCells = nil;

In order for this to work we must create a new mutable array and fill it with HorizontalTableCell instances so that our table view can use them when necessary.

However, we have to write our initialization code within the ArticleListViewController_iPhone.m’s viewDidLoad method and not on the standard ArticleListViewController, that’s because we want to create instances of HorizontalTableCell_iPhone for the iPhone and, later on, HorizontalTableCell_iPad for the iPad version.

Jump over to ArticleListViewController_iPhone.m and add the following code to your viewDidLoad method:

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    if (!self.reusableCells)
    {       
        NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:nil ascending:YES selector:@selector(localizedCompare:)];
        NSArray* sortedCategories = [self.articleDictionary.allKeys sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
 
        NSString *categoryName;
        NSArray *currentCategory;
 
        self.reusableCells = [NSMutableArray array];
 
        for (int i = 0; i < [self.articleDictionary.allKeys count]; i++)
        {                        
            HorizontalTableCell_iPhone *cell = [[HorizontalTableCell_iPhone alloc] initWithFrame:CGRectMake(0, 0, 320, 416)];
 
            categoryName = [sortedCategories objectAtIndex:i];
            currentCategory = [self.articleDictionary objectForKey:categoryName];
            cell.articles = [NSArray arrayWithArray:currentCategory];
 
            [self.reusableCells addObject:cell];
            [cell release];
        }
    }
}

When we first enter our viewDidLoad method we call our superclass’ viewDidLoad, just standard procedure here. Afterwards we check to see if we already have created our reusable cells or not, in case we haven’t we proceed with the creation and initialization.

As you have seen before we create an NSSortDescriptor to sort our categories and store all of the sorted dictionary keys in an array. The reason for this is so that our reusableCells array contains cells already sorted in the same order as our categories, that way we just get the object at the necessary index and no further process needs to be done.

Next up we declare a categoryName string and a currentCategory array, to variables that will be useful in our loop coming up next.

In our loop we are just going to go over all of the possible dictionary keys (for each category) and create a reusable cell for each. Inside the loop we create a HorizontalTableCell_iPhone instance, initialize it with a top left position and a width and height (don’t worry about this since we overrode the initWithFrame method and it will pass in the proper frame size).

After that we just get the category array for this corresponding cell, store it within it’s articles property, add the cell we just created to our reusable cells array and release it (remember that by default when you pass add an object to an array it retains it).

Here’s where the cool part comes in, whilst still inside the ArticleListViewController_iPhone.m file, go to the cellForRowAtIndexPath method and replace it with the following code:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{        
    HorizontalTableCell *cell = [self.reusableCells objectAtIndex:indexPath.section];
 
    return cell;
}

Yup, that’s all we need. Since we already created a cache of reusable cells, all we need to do is fetch the appropriate one.

Let’s perform some cleanup now, go to the HorizontalTableCell.m file and delete the reuseIdentifier method. Since we are no longer using the provided method for reusable cells, we don’t need an identifier for them.

We are almost done!!! But before let’s build and run our project to see what we get:

Fix due to table view cell reuse - now showing correct info.

Yay!!! Our interface works to perfection :) pretty cool huh?

Now for a final performance tweak we are going to get a little help from GCD (Grand Central Dispatch).

Note: For more information on GCD and a more in depth look check out the Multithreading And Grand Central Dispatch On iOS For Beginners Tutorial available right here at Ray’s website!

Go over to the HorizontalTableCell_iPhone.m file and find your cellForRowAtIndexPath method, add the following code:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ArticleCell";
 
    __block ArticleCell_iPhone *cell = (ArticleCell_iPhone *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
 
    if (cell == nil) 
    {
        cell = [[[ArticleCell_iPhone alloc] initWithFrame:CGRectMake(0, 0, kCellWidth, kCellHeight)] autorelease];
    }
 
    __block NSDictionary *currentArticle = [self.articles objectAtIndex:indexPath.row];
 
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
    dispatch_async(concurrentQueue, ^{        
        UIImage *image = nil;        
        image = [UIImage imageNamed:[currentArticle objectForKey:@"ImageName"]];
 
        dispatch_async(dispatch_get_main_queue(), ^{
            [cell.thumbnail setImage:image]; 
        });
    }); 
 
    cell.titleLabel.text = [currentArticle objectForKey:@"Title"];
 
    return cell;
}

We have made a few changes to the regular method so let’s go over them one at a time.

First up we added __block prior to our ArticleCell_iPhone cell variable, that’s going to allow us to use the cell variable inside blocks. We continue with the standard check to see if we can dequeue a reusable cell or if we have to create one.

After that we create a new dispatch queue variable and as GCD to give us one of the available queues.

The interesting part is inside the dispatch_async method, all calls and process regarding UI must be performed on the main thread, but we want to load our images asynchronously in a background thread so that when our users open the app they can continue to scroll and navigate without having to wait a few seconds until all of the images are loaded.

In order to do this we use dispatch_async to run the UIImage creation in the background. Once that is done we go ahead and dispatch another asynchronous process but this time we use dispatch_get_main_queue() to get the main thread (remember that we can’t make changes to the interface on background threads) and set the thumbnail image to the one we just loaded asynchronously.

Outside of the dispatch blocks we set the title text as usual because we don’t need to load that, just read it from our array of articles.

AND WE ARE DONE! Run your app one more time and you might notice a smoother load and scroll right from the start (if not wait until you try this with 100 images, it truly makes a difference!)

:D

Congratulations on making it this far, the second part definitely involved fewer work designing interfaces and more under the hood code. In order to keep this tutorial nice and simple we will not be going over the iPad process even though it’s exactly the same (don’t worry it’s included within the source code), you can do that as homework in order to practice and further understand the project! :)

Where To Go From Here?

Here is an example project with all of the code from the above tutorial, plus the iPad mods.

First off, I recommend you repeat this for the iPad and make the interface customized for the larger screen. You can find the iPad constants within the project bundle, otherwise go ahead and download the code and just run it.

Here are some ideas that might interest you:

  • Add a selection to the articles so it loads a new view with the text, image and title
  • Load news from an RSS feed and display them with your new interface
  • Play around with the sizes, colors and values of the interface to your liking and to see what you can come up with

How cool was this tutorial? I hope you enjoyed it as much as I did. Feel free to post your questions and comments on the forums, I can’t wait what you guys and gals come up with for your next great app! :)


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

Ray Wenderlich

Ray is an indie software developer currently focusing on iPhone and iPad development, and the administrator of this site. He’s the founder of a small iPhone development studio called Razeware, and is passionate both about making apps and teaching others the techniques to make them.

When Ray’s not programming, he’s probably playing video games, role playing games, or board games.

User Comments

135 Comments

[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]
  • I was working with this project. I have created a details page on top of it. I am able to access the index.row value, however I need to get the section value. Can you please help me?
    Amitabha.saha
  • The table of articles within each HorizontalTableCell only has one section. To get the section number from the enclosing table in ArticleListViewController, try the following steps.

    Add an NSInteger property named section to HorizontalTableCell.h
    Code: Select all
    @property (nonatomic,assign) NSInteger section;

    In the cellForRowAtIndexPath method of ArticleListViewController_iPhone (and _iPad), set the value of that property to the section # of the article table view
    Code: Select all
    cell.section = indexPath.section;


    With those changes in place, the tableView:didSelectRowAtIndexPath method in HorizontalTableCell.m can retrieve the section with self.section.
    Code: Select all
    NSLog("Section number is %d", self.section);


    Have fun!
    Richard Caseyrcasey
  • Any tips on how to update this for ARC?

    Once updated, it SIGABRTs right in main.


    Also, when searching this site for "horizontal", this great article is not found.
    zav
  • Great example. I have one question though, why does the horizontal scroll view not swipe beyonds its bounds e.g left and right. If you are already scrolling it will pull the horizontal content past its cut off point. If you try to start a swipe by pulling it past its bounds, (Like a table view pull down to refresh) it ignores the touch. I have looked through the comments and seen this same issue mentioned a few times, but no fix, any ideas anyone?

    Thanks
    jon gaukroger
  • Hi I want to display multiple images in 2 * X format like this

    1111111 // item 1 should be scrolloing horizontally altogether
    1111111 //
    1111111 //
    2222222 // item 2 should be scrolloing horizontally altogether
    2222222
    2222222
    3333333 // item 3 should be scrolloing horizontally altogether
    3333333
    3333333

    your app is working like this
    111111
    222222
    333333
    444444
    emysa341
  • @emysa341

    Earlier in this thread there is a discussion on how to get all the rows to scroll horizontally when any one row is scrolled.

    See http://www.raywenderlich.com/forums/viewtopic.php?f=20&t=1023&start=110#p37580

    That technique could be used to scroll only selected rows.

    Have fun!
    Richard Caseyrcasey
  • As usual, another fantastic tutorial and walkthrough from your group. This was a particularly irksome challenge till I came across this post. I do have a question surrounding search bar integration. Any thoughts about what it might take to get a search bar integrated with the tableview. I have already moved the tableview to a childview within another viewcontroller which works exceedingly well.
    tzelathiel
  • employee
    ankit.jain
  • Great Tutorial, thanks!
    Is there a similar tutorial for Android?
    Thanks you!
    avillas
  • i have to push another view from selecting on different section on didselectMethod.
    So please give me a solution
    Mail me on : parthpatel1105@gmail.com
    Thanks.
    parthpatel1105
  • Thank you for this great tutorial, however i have a question related to the tableview, how to make links to those objects in tableview to new page? i want to tap on an image and go to new view controller.
    Dan_za_man
  • Awesome tutorial. Love the cleverness and simplicity of two counter-directional rotations.

    Just wanted to let you know real quick that this trick works really well also when using Storyboards with Autolayout. I skimmed through this tutorial, picking out the few key points that I needed to implement this concept for a project I'm working on. The project is using Storyboards, and I ended up putting one table view inside another, and it all just worked. When I got to the point of rotating the final Cell views back so they are straight up again, I was worried that I would have problems with Autolayout, but to my pleasant surprise the Autolayout actually made it a lot easier. I did not have to write any code to adjust view frames at all, it just came out great. :-)

    Thanks for the inspiration, and the beautifully detailed tutorial. This site is truly awesome..!

    Erik
    vanderneut
  • Hello, I used your tutorial to help create a project I'm working on (which did exactly what you said it would!) but in my design for the individual "ArticleCells" I have a 'Time' Label and a 'Count' label for allowing users to click the cell which essentially adds 1 to the Count (for scheduling purposes). My question is how can I make it so once the cell is clicked and I do the 'add 1' code that the cells will refresh with the new number?
    schwoo
  • Hi, When I need to add 2 rows in each section, so the first row are blank without any cell
    so what I need to change to work on it
    houssam
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Loading ... Loading ...

Last week's winner: How To Make a Tower Defense Game with Swift.

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in December: The Great CALayer Tour

Sign Up - December

Our Books

Our Team

Tutorial Team

  • Matt Luedke

... 60 total!

Update Team

  • Zouhair Mahieddine

... 14 total!

Editorial Team

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

  • Richard Casey

... 4 total!