Rss Reader Tutorial for iOS: How To Make A Simple RSS Reader iPhone App

Learn how to make a simple RSS reader in this iPhone app Tutorial. By Ray Wenderlich.

Leave a rating/review
Save for later
Share

We'll make a multithreaded RSS reader in this tutorial!

We'll make a multithreaded RSS reader in this tutorial!

When making iOS apps that have to download data off of the Internet, you run into a lot of challenges. You have to:

  • Write code to retrieve the data off of the network
  • Write code to parse and interpret the data
  • Write code to run the above in the background, so your app remains responsive
  • Update your UI in an animated fashion as data arrives so the user can see what’s going on

That’s a lot of different concepts to put together. So in this iPhone app tutorial, you’ll get hands-on experience doing exactly that by making a simple RSS reader app!

This iPhone app tutorial was specially requested and sponsored by William Mottl, a kind supporter of this blog. Thank you William!

Getting Started

Start up XCode, go to File\New Project, choose iOS\Application\Navigation-based Application, and click Choose. Name the project RSSFun, and click Save.

The first thing you’ll do is create a class to keep track of individual articles inside a RSS feed. Select the Classes group, go to File\New File, choose iOS\Cocoa Touch Class\Objective-C class, and click Next. Name the class RSSEntry.m, make sure “Also create RSSEntry.h” is checked, and click Finish.

Replace RSSEntry.h with the following:

#import <Foundation/Foundation.h>

@interface RSSEntry : NSObject {
    NSString *_blogTitle;
    NSString *_articleTitle;
    NSString *_articleUrl;
    NSDate *_articleDate;
}

@property (copy) NSString *blogTitle;
@property (copy) NSString *articleTitle;
@property (copy) NSString *articleUrl;
@property (copy) NSDate *articleDate;

- (id)initWithBlogTitle:(NSString*)blogTitle articleTitle:(NSString*)articleTitle articleUrl:(NSString*)articleUrl articleDate:(NSDate*)articleDate;

@end

As you can see, this is a very simple class that just stores information about each article: its title, url, and date, as well as the name of the blog it came from.

Next replace RSSEntry.m iwth the following to complete the implementation:

#import "RSSEntry.h"

@implementation RSSEntry
@synthesize blogTitle = _blogTitle;
@synthesize articleTitle = _articleTitle;
@synthesize articleUrl = _articleUrl;
@synthesize articleDate = _articleDate;

- (id)initWithBlogTitle:(NSString*)blogTitle articleTitle:(NSString*)articleTitle articleUrl:(NSString*)articleUrl articleDate:(NSDate*)articleDate 
{
    if ((self = [super init])) {
        _blogTitle = [blogTitle copy];
        _articleTitle = [articleTitle copy];
        _articleUrl = [articleUrl copy];
        _articleDate = [articleDate copy];
    }
    return self;
}

- (void)dealloc {
    [_blogTitle release];
    _blogTitle = nil;
    [_articleTitle release];
    _articleTitle = nil;
    [_articleUrl release];
    _articleUrl = nil;
    [_articleDate release];
    _articleDate = nil;
    [super dealloc];
}

@end

Nothing fancy here – this just synthesizes the properties and creates an initializer for convenience.

Next let’s set up the table view controller so that it keeps a list of RSS entries, and displays information about each one in the table. We’ll put some dummy entries in for now, and later on we’ll add the code to retrieve them from actual RSS feeds on the Internet.

So go to RootViewController.h and make the following changes:

// Inside @interface
NSMutableArray *_allEntries;

// After @interface
@property (retain) NSMutableArray *allEntries;

This instance variable and property will be used to keep a list of all RSS entries. Next make the following changes to RootViewController.m:

// At top of file
#import "RSSEntry.h"

// After @implementation
@synthesize allEntries = _allEntries;

// Add new method
- (void)addRows {    
    RSSEntry *entry1 = [[[RSSEntry alloc] initWithBlogTitle:@"1" 
                                               articleTitle:@"1" 
                                                 articleUrl:@"1" 
                                                articleDate:[NSDate date]] autorelease];
    RSSEntry *entry2 = [[[RSSEntry alloc] initWithBlogTitle:@"2" 
                                               articleTitle:@"2" 
                                                 articleUrl:@"2" 
                                                articleDate:[NSDate date]] autorelease];
    RSSEntry *entry3 = [[[RSSEntry alloc] initWithBlogTitle:@"3" 
                                               articleTitle:@"3" 
                                                 articleUrl:@"3" 
                                                articleDate:[NSDate date]] autorelease];    
    
    [_allEntries insertObject:entry1 atIndex:0];
    [_allEntries insertObject:entry2 atIndex:0];
    [_allEntries insertObject:entry3 atIndex:0];        
}

// Uncomment viewDidLoad and make it look like the following
- (void)viewDidLoad {
    [super viewDidLoad];

    self.title = @"Feeds";
    self.allEntries = [NSMutableArray array];
    [self addRows];    
}

// Replace return 0 in tableView:numberOfRowsInSection with this
return [_allEntries count];

// Modify tableView:cellForRowAtIndexPath below like the following
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
    }
    
    RSSEntry *entry = [_allEntries objectAtIndex:indexPath.row];

    NSDateFormatter * dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
    [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
    [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
    NSString *articleDateString = [dateFormatter stringFromDate:entry.articleDate];

    cell.textLabel.text = entry.articleTitle;        
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ - %@", articleDateString, entry.blogTitle];

    return cell;
}

// In dealloc
[_allEntries release];
_allEntries = nil;

Most of this is straightforward, but here are a few notes of explanation:

  • addRows is a helper method to create three dummy entries into the list just to make sure the display code is working.
  • The table view is set up to display the entries by modifying tableView:numberOfRowsInSection to return the number of rows in the allEntries array, and tableView:cellForRowAtIndexPath is modified to use the UITableViewCellStyleSubtitle style, and sets up the title and subtitle fields according to the current entry.
  • Note that to display an NSDate as a string, you should use the NSDateFormatter class. You can specify default styles like we do here (NSDateFormatterMediumStyle), or specify custom styles as well.

Compile and run your code, and you should now see three test entries in the list!

RSS Reader with dummy data

Downloading RSS Feeds Asynchronously with ASIHTTPRequest

You can download data from the Internet directly with the iOS SDK with NSURLRequest and NSURLConnection, but I prefer to use a set of helper classes written by Ben Copsey called ASIHTTPRequest. It simplifies things and has a lot of neat and helpful features.

The ASIHTTPRequest page has great instructions on how to integrate the code into your project, but I’ll post the steps here as well for quick reference:

  1. Go to to your RSSFun project directory in Finder, and make a new folder called ASIHTTPRequest, as a sibling to your Classes Folder.
  2. Now download the ASIHTTPRequest source.
  3. Untar the file to expand the source into a folder.
  4. Copy all of the files from the Classes directory that begin with ASI (but not the folders) to your RSSFun\ASIHTTPRequest folder.
  5. Also copy all of the files from the External\Reachability directory to your RSSFun\ASIHTTPRequest folder.
  6. Back in XCode, right control-click the RSSFun entry at the top of the Groups & Files tree, and choose Add\New Group. Name the new group ASIHTTPRequest.
  7. Control-click your new ASIHTTPRequest group, and choose Get Info. In the General tab, click the Choose button, and browse to your RSSFun\ASIHTTPRequest folder to link the group to your file system. When you’re done, close the dialog.
  8. Control-click your new ASIHTTPRequest gorup again, and choose Add\Existing Files. Select all of the files in RSSFun\ASIHTTPRequest, and click Add, and Add again.
  9. Finally, expand Targets and double click on the RSSFun target. In the General tab under Linked Libraries, click the plus button. Select CFNetwork.framework, and click Add. Then repeat for SystemConfiguration.framework, MobileCoreServices.framework, and libz.1.2.3.dylib. When you’re done, close the dialog.

Phew! Do a quick compile to make sure everything’s OK so far, then it’s time to add some code to try out ASIHTTPRequest.

First, modify RootViewController.h to add two new instance variables/properties:

// Inside @interface
NSOperationQueue *_queue;
NSArray *_feeds;

// After @interface
@property (retain) NSOperationQueue *queue;
@property (retain) NSArray *feeds;

The first instance variable is an NSOperationQueue. If you are not familiar with NSOperationQueue, it’s a class that lets you queue operations – basically bits of code that need to be run at some point. NSOperationQueue will then run the tasks in the background, as system resources allow.

NSOperationQueue is basically a layer on top of threads. It’s a highly configurable class with some neat functionality, but for this project we just need a default NSOperationQueue because we want to run some code in the background easily.

The feeds array is just an NSArray that will hold the URLs for the RSS feeds we wish to display. We’ll just hardcode in some feeds for the purposes of this app.

Next, switch to RootViewController.m and make the following changes:

// Add to top of file
#import "ASIHTTPRequest.h"

// Add under @implementation
@synthesize feeds = _feeds;
@synthesize queue = _queue;

// Add new method
- (void)refresh {
    for (NSString *feed in _feeds) {
        NSURL *url = [NSURL URLWithString:feed];
        ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
        [request setDelegate:self];
        [_queue addOperation:request];
    }    
}

// Modify viewDidLoad as follows
- (void)viewDidLoad {
    [super viewDidLoad];    
    self.title = @"Feeds";
    self.allEntries = [NSMutableArray array];
    self.queue = [[[NSOperationQueue alloc] init] autorelease];
    self.feeds = [NSArray arrayWithObjects:@"http://feeds.feedburner.com/RayWenderlich",
                  @"http://feeds.feedburner.com/vmwstudios",
                  @"http://idtypealittlefaster.blogspot.com/feeds/posts/default", 
                  @"http://www.71squared.com/feed/",
                  @"http://cocoawithlove.com/feeds/posts/default",
                  @"http://feeds2.feedburner.com/brandontreb",
                  @"http://feeds.feedburner.com/CoryWilesBlog",
                  @"http://geekanddad.wordpress.com/feed/",
                  @"http://iphonedevelopment.blogspot.com/feeds/posts/default",
                  @"http://karnakgames.com/wp/feed/",
                  @"http://kwigbo.com/rss",
                  @"http://shawnsbits.com/feed/",
                  @"http://pocketcyclone.com/feed/",
                  @"http://www.alexcurylo.com/blog/feed/",         
                  @"http://feeds.feedburner.com/maniacdev",
                  @"http://feeds.feedburner.com/macindie",
                  nil];    
    [self refresh];
}

// In dealloc
[_queue release];
_queue = nil;
[_feeds release];
_feeds = nil;

Let’s stop here for a moment before we continue on. To use ASIHTTPRequest, you first need to import the header file, so we do so here. We also synthesize the two new properties.

Next, there’s a method called refresh that starts the downloading of the RSS feeds in the background. This is very easy to do with ASIHTTPRequest. You simply need to create an ASIHTTPRequest object, and start it running. For this iPhone app tutorial, we’re going to start it running by adding it as an operation to our operation queue, so the system can efficiently manage the threading behavior for us.

So the refresh method loops through the list of feeds, and calls ASIHTTPRequest requestWithURL for each URL to create a request that will download the data, and queues it up to run at some point by adding it to the operation queue.

When the data finishes downloading, by default ASIHTTPRequest will call methods named requestFinished or requestFailed on its delegate, so we set ourselves as the delegate (and we’ll write those methods next).

Also, we modify viewDidLoad here to initialize a default NSOperationQueue, and hard-code some RSS feeds to retrieve.

Ok, now let’s implement the requestFinished and requestFailed methods, that will be called after the data is downloaded for each RSS feed:

- (void)requestFinished:(ASIHTTPRequest *)request {
            
    RSSEntry *entry = [[[RSSEntry alloc] initWithBlogTitle:request.url.absoluteString
                                               articleTitle:request.url.absoluteString
                                                 articleUrl:request.url.absoluteString
                                                articleDate:[NSDate date]] autorelease];    
    int insertIdx = 0;                    
    [_allEntries insertObject:entry atIndex:insertIdx];
    [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:insertIdx inSection:0]]
                          withRowAnimation:UITableViewRowAnimationRight];
                                                         
}

- (void)requestFailed:(ASIHTTPRequest *)request {
    NSError *error = [request error];
    NSLog(@"Error: %@", error);
}

After the data finishes downloading, we can access the downloaded data with [request responseString] or [request responseData]. But right now, we aren’t really going to worry about the content of the data – instead we’ll just update the table view with a dummy entry so we can visually see that the data has downloaded.

So when requestFinished is called, it creates a new RSSEntry that eventually needs to be added to the allEntries array and displayed in the table view. This callback method is called on the main thread, so it’s safe to update the allEntries array and table view directly.

Note that the table view is updated with insertRowsAtIndexPaths rather than just calling reloadData. This makes for a nice animated display as new rows come in, rather than a disruptive reloadData.

Compile and run the code, and you should now see dummy entries for each feed fly in as the data is downloaded!

RSS feeds retrieved asynchronously with ASIHTTPRequest