Dynamically loading new rows into a table

A pattern that is common to many mobile apps is to allow the user to scroll down a long list of items that must be retrieved over the network from a remote server. Retrieving all of the items up front would be slow and give a very poor user experience. What typically happens is that a block of items is retrieved and presented to the user in a table view. The last row in the table shows a “Load More…” message which when tapped triggers the retrieval of the next block of items which are then added to the end of the table.

This approach can be seen in Apple’s own App Store and You Tube applications. It is also employed by HTML-based web applications such as Google Reader. It has the advantage of minimising the network traffic and also of not making the user wait too long before seeing the table updated.

Example App: Feeder

To explore how to implement a table view that dynamically loads new rows I will build an example RSS feed reader application. Given a particular RSS feed my app should retrieve a defined number of records from the feed and allow the user to view each individual post. Tapping the “Load More…” cell in the table view should update the table with any additional items in the feed.

To get started I am going to cheat by avoiding the complexity of actually retrieving and parsing an RSS feed and focus on the implementation of the table view. I may come back to the network and parsing parts of the app in future posts but for now I will just simulate this with some sample data. The objective in this first post is to implement a basic table view with a “Load More…” option that can be used to navigate a large collection of items.

Getting Started

I used the Navigation-based Application template to create a new X-code project which contains a root table view controller to show what will be the RSS Feed and then a detailed view controller to show individual posts that are selected from the root table view. I will skip some of the project setup. The basic view hierarchy as seen in Interface Builder (IB) is as follows:

The root view controller is called FeedViewController and implements the top level table view to show the index of posts in the RSS Feed. We will come back to the details of this controller in a minute but first I want to look at the basic objects we need for the model part of our model-view-controller (MVC) design.

Post Model

Since the whole point of this app is to show the contents of posts in an RSS feed I know that I will need a post class. To keep things simple in this initial version I am only going to include the most basic of items - a post title, a post description and a publication date. The contents of Post.h are shown below:

@interface Post : NSObject {
  NSString *postTitle;
  NSString *postDescr;
  NSDate *pubDate;
}

@property (copy, nonatomic) NSString *postTitle;
@property (copy, nonatomic) NSString *postDescr;
@property (copy, nonatomic) NSDate *pubDate;

@end

The only thing worth noting is that the instance variable properties are defined as using a copy setter. This means that when we assign something to a Post object ivar a copy of the object is made and kept inside the post object rather than keeping a reference to an external object. This ensures our model cannot be changed after the fact by some external class modifying the original object.

The implementation of a Post object is currently trivial, it synthesises the ivar properties and releases everything when the object is deallocated.

Feed Model

In addition to modelling the individual posts I also need an object to hold the details about the whole feed. In a full implementation this would contain details on the feed as well as an index of posts as they are retrieved from the remote server. Since I am currently only going to simulate the retrieval of data my feed object is pretty much empty. Actually in this initial version it has no ivars at all and only a single instance method to fetch new posts:

@interface Feed : NSObject {
}

- (NSArray *)newPosts;
@end

The implementation of the newPosts instance method is as follows:

- (NSArray *)newPosts {
  NSMutableArray *posts = [[NSMutableArray alloc] init];
  
  for (NSInteger item = 1; item <= 5; item++) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    Post *newPost = [[Post alloc] init];
    NSDate *pubDate = [NSDate dateWithTimeIntervalSinceNow:item];
    newPost.postTitle = [NSString stringWithFormat:@"Article %@", pubDate];
    newPost.postDescr = @"Some text describing this post...";
    newPost.pubDate = pubDate;
    
    [posts addObject:newPost];
    [newPost release];
    
    [pool drain];
  }
  return posts;
}

For now this method just allocates and returns to the caller an array filled with five dummy Post objects. Following standard Cocoa naming conventions the method name starts with “new” to indicate to the caller should expect to get back allocated objects that it must take responsibility for and ultimately release when no longer required.

The only other point of interest is the use of an autorelease pool inside the loop that allocates Post objects. This is not really necessary in this case since we are only building five post objects. However if we change this code to start creating 500 posts the memory requirements for the temporary NSDate and NSString objects which are autoreleased could start to become a problem. You can find a more detailed discussion of using autorelease pools in this earlier post on Cocoa Factory Classes.

Implementing the Feed Table View

With our basic model classes setup we can implement the table view used to show the list of posts in our dummy RSS feed. As we saw earlier our root table view controller class is named FeedViewController. The interface for this class is pretty simple:

@interface FeedViewController : UITableViewController {
  Feed *feed;
  NSMutableArray *posts;
}

@property (nonatomic, retain) Feed *feed;
@property (nonatomic, retain) NSMutableArray *posts;

Our interface consists of a feed object to allow us to interact with the RSS feed and an NSMutableArray which we will use to store the posts we retrieve from the feed. This is sufficient for now to allow us to experiment with the table view. If we start wanting to store the retrieved posts when the application terminates or retain large numbers of posts in memory we may want to replace this simple storage model with a core data backed storage. However for now we will keep things simple. Actually this is really not a bad approach when building an App. We don’t want to add too much complexity too soon until we get a feel for which direction the App is taking.

Generating Some Data

In order to play with our table view we need to populate the objects in our feed controller. A good place to do this initialisation is in the viewDidLoad method of the FeedViewController:

- (void)viewDidLoad {
  [super viewDidLoad];
  
  self.feed = [[Feed alloc] init];
  self.posts = [[NSMutableArray alloc] init];
  
  NSArray *newPosts = [feed newPosts];
  
  if (newPosts) {	
    [self.posts addObjectsFromArray:newPosts];
    [newPosts release];
  }
}

The first thing we do is allocate and initialise a feed object. In a real implementation this would most likely trigger some network communication with a remote server but for now it really does nothing much. We also allocate a mutable array to hold the posts we have retrieved and then use the newPosts method we saw previously to get the feed object to fetch us some posts.

Implementing the Table View Data Source Methods

To get our data into the table view we need to implement as a minimum the two required UITableViewDataSource protocol methods (our FeedViewController class will act as both the table data source and table delegate):

- (NSInteger)tableView:(UITableView *)tableView
  numberOfRowsInSection:(NSInteger)section {
  return [posts count] + 1;
}

The first method is simple and determines how many rows we have in each section of our table. Our table has a single section and needs to have one row for every post that we have retrieved from the feed plus one. The plus one is to allow for an extra row at the end of the table to show the “Load More…” message.

As an aside before we see the second required method I have also implemented the optional method to return the number of sections in the table:

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

This method is not really required in this case since a table will default to 1 section if this method is not implemented. The second required method that we must implement is to return a cell for each row in the table when requested:

- (UITableViewCell *)tableView:(UITableView *)tableView 
                     cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *postCellId = @"postCell";
    static NSString *moreCellId = @"moreCell";
    UITableViewCell *cell = nil;
  
    NSUInteger row = [indexPath row];
    NSUInteger count = [posts count];
  
    if (row == count) {
    
       cell = [tableView dequeueReusableCellWithIdentifier:moreCellId];
       if (cell == nil) {
           cell = [[[UITableViewCell alloc] 
                     initWithStyle:UITableViewCellStyleDefault 
                     reuseIdentifier:moreCellId] autorelease];
       }
    
       cell.textLabel.text = @"Load more items...";
       cell.textLabel.textColor = [UIColor blueColor];
       cell.textLabel.font = [UIFont boldSystemFontOfSize:14];
       
    } else {
  
        cell = [tableView dequeueReusableCellWithIdentifier:postCellId];
        if (cell == nil) {
            cell = [[[UITableViewCell alloc] 
                     initWithStyle:UITableViewCellStyleSubtitle 
                     reuseIdentifier:postCellId] autorelease];
        }

        Post *currentPost = [posts objectAtIndex:row];
        cell.textLabel.text = [currentPost postTitle];
        cell.textLabel.font = [UIFont systemFontOfSize:14];
    
        cell.detailTextLabel.text = [currentPost postDescr];
        cell.detailTextLabel.font = [UIFont systemFontOfSize:10];
    }
  
    return cell;
}

To retrieve new posts we just need to send the newPosts message to our feed object and see if we get back an array containing some posts. If we have some new posts to add to the table we could at this point simply call [self.tableView reloadData] and when the table refreshes it will show the new posts.

In this case I have made things a bit more complicated by animating the insertion of the new rows into the table. To do this we need to build an array of NSIndexPath objects for each new row. Then we use the insertRowsAtIndexPaths method to fade the rows into view. This method should be preceded by the beginUpdates method and followed by the endUpdates method.

Finally we scroll the table so that the first newly inserted row is in the middle of the screen and we deselect any previously selected row.

In Summary

Implementing a table that can trigger the loading of new rows is pretty straightforward and is a very useful technique anytime you are relying on retrieving large amounts of data from a remote server. This is already a fairly long post so I will follow up in a future post with some further refinements to the table view.