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

This is a blog post by iOS Tutorial Team member Felipe Laso, an independent iOS developer and aspiring game designer/programmer. 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 […] By Ray Wenderlich.

Leave a rating/review
Save for later
Share

Contents

Hide contents

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.

Contributors

Over 300 content creators. Join our team.