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
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Parsing the Feeds with GDataXML

RSS feeds are basically XML in one of two formats: RSS and Atom. One fast and easy API you can use to parse XML in iOS is GDataXML, so we’ll use that here.

To integrate GDataXML into your project, take the following steps:

  1. Go to to your RSSFun project directory in Finder, and make a new folder called GDataXML, as a sibling to your Classes Folder.
  2. Download the gdata-objective-c client library.
  3. Unzip the file, navigate to Source\XMLSupport, and copy the two files into your RSSFun\GDataXML folder.
  4. 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 GDataXML.
  5. Control-click your new GDataXML group, and choose Get Info. In the General tab, click the Choose button, and browse to your RSSFun\GDataXML folder to link the group to your file system. When you’re done, close the dialog.
  6. Control-click your new GDataXML gorup again, and choose Add\Existing Files. Select all of the files in RSSFun\ GDataXML, and click Add, and Add again.
  7. Click Project\Edit Project Settings, go to the Build tab, and make sure “All Configurations” are checked.
  8. Find the Search Paths\Header Search Paths setting and add /usr/include/libxml2 to the list.
  9. Finally, find the Linking\Other Linker Flags section and add -lxml2 to the list.

Compile the project to make sure everything builds OK – then it’s time to add some code.

In GDataXML, the class that you use to work with XML elements is, not surprisingly, called GDataXMLElement. We’ll be using this a lot while parsing XML, so let’s add a little helper method to the class to save ourself some repetitive code. So control-click on GDataXML, choose Add\New File, choose iOS\Cocoa Touch Class\Objective-C class, and click Next. Name the class GDataXMLElement-Extras.m, make sure “Also create GDataXMLElement-Extras.h” is checked, and click Finish.

Replace GDataXMLElement-Extras.h with the following:

#import <Foundation/Foundation.h>
#import "GDataXMLNode.h"

@interface GDataXMLElement (Extras)

- (GDataXMLElement *)elementForChild:(NSString *)childName;
- (NSString *)valueForChild:(NSString *)childName;

@end

This defines the two helper methods we’re adding – one to get a single GDataXMLElement for a child with a given name, and one to get a string value for a specified child.

Next add the implementation by replacing GDataXMLElement-Extras.m:

#import "GDataXMLElement-Extras.h"

@implementation GDataXMLElement(Extras)

- (GDataXMLElement *)elementForChild:(NSString *)childName {
    NSArray *children = [self elementsForName:childName];            
    if (children.count > 0) {
        GDataXMLElement *childElement = (GDataXMLElement *) [children objectAtIndex:0];
        return childElement;
    } else return nil;
}

- (NSString *)valueForChild:(NSString *)childName {    
    return [[self elementForChild:childName] stringValue];    
}

@end

The first method uses the built-in elementsForName method to get all of the child elements of the current node given the spcified name. If there’s at least one, it returns the first child, otherwise it returns nil. Similarly, valueForChild calls elementForChild, then stringValue on the result. This is very simple stuff – but again, we’re just adding it to save ourselves from having to type this over and over.

Next, make the following changes to RootViewController.m:

// Add to top of file
#import "GDataXMLNode.h"
#import "GDataXMLElement-Extras.h"

// Modify requestFinished as follows
- (void)requestFinished:(ASIHTTPRequest *)request {
           
    [_queue addOperationWithBlock:^{
        
        NSError *error;
        GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:[request responseData] 
                                                               options:0 error:&error];
        if (doc == nil) { 
            NSLog(@"Failed to parse %@", request.url);
        } else {
            
            NSMutableArray *entries = [NSMutableArray array];
            [self parseFeed:doc.rootElement entries:entries];                
            
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                
                for (RSSEntry *entry in entries) {
                    
                    int insertIdx = 0;                    
                    [_allEntries insertObject:entry atIndex:insertIdx];
                    [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:insertIdx inSection:0]]
                                          withRowAnimation:UITableViewRowAnimationRight];
                    
                }                            
                
            }];
            
        }        
    }];
                                                             
}

Ok, so some major changes to requestFinished to add the code to parse the XML feed.

The first thing we do is queue up the code to parse the XML feed to be run in the background. This is because parsing the XML feed might take a while, and we don’t want the UI to hang as it’s parsing the XML.

Again, we can make use of the operation queue to do this, by adding some code to be run on the queue. In iOS4, there’s a helper method that makes this extremely easy to do called addOperationWithBlock, so we make use of that here.

Inside the block of code to be run, we parse the document by calling GDataXML’s initWithData method, passing it the data read from the network ([request responseData]). If it successfully parses into memory as a DOM model, we call a helper method (which we’re about to write) called parseFeed, passing in the root element and an array it should add any articles into as RSSEntries.

After the RSSEntries are added to the array, we need to update the table view. But because we queued up the operation to run on the operation queue in the background, we can’t update the UI or our allEntries array, since a) you can’t update the UI on a background thread, and b) our allEntries array is not protected by a mutex, so should only be accessed on the main thread.

So we get around this by simply adding another block of code to be run on [NSOperationQueue mainQueue], which is the same as the main thread. This routine takes the entries to display, and adds them into the allEntries array and also keeps the tableView up to date.

Ok next up, adding the helper method to parse a RSS feed:

- (void)parseFeed:(GDataXMLElement *)rootElement entries:(NSMutableArray *)entries {    
    if ([rootElement.name compare:@"rss"] == NSOrderedSame) {
        [self parseRss:rootElement entries:entries];
    } else if ([rootElement.name compare:@"feed"] == NSOrderedSame) {                       
        [self parseAtom:rootElement entries:entries];
    } else {
        NSLog(@"Unsupported root element: %@", rootElement.name);
    }    
}

This looks at the root element to see if it’s rss (RSS format) or feed (Atom format) and calls the appropriate helper method for each.

Next, add the parsing code for each method as follows:

- (void)parseRss:(GDataXMLElement *)rootElement entries:(NSMutableArray *)entries {
    
    NSArray *channels = [rootElement elementsForName:@"channel"];
    for (GDataXMLElement *channel in channels) {            
        
        NSString *blogTitle = [channel valueForChild:@"title"];                    
        
        NSArray *items = [channel elementsForName:@"item"];
        for (GDataXMLElement *item in items) {
            
            NSString *articleTitle = [item valueForChild:@"title"];
            NSString *articleUrl = [item valueForChild:@"link"];            
            NSString *articleDateString = [item valueForChild:@"pubDate"];        
            NSDate *articleDate = nil;
            
            RSSEntry *entry = [[[RSSEntry alloc] initWithBlogTitle:blogTitle 
                                                      articleTitle:articleTitle 
                                                        articleUrl:articleUrl 
                                                       articleDate:articleDate] autorelease];
            [entries addObject:entry];
            
        }      
    }
    
}

- (void)parseAtom:(GDataXMLElement *)rootElement entries:(NSMutableArray *)entries {
    
    NSString *blogTitle = [rootElement valueForChild:@"title"];                    
    
    NSArray *items = [rootElement elementsForName:@"entry"];
    for (GDataXMLElement *item in items) {
        
        NSString *articleTitle = [item valueForChild:@"title"];
        NSString *articleUrl = nil;
        NSArray *links = [item elementsForName:@"link"];        
        for(GDataXMLElement *link in links) {
            NSString *rel = [[link attributeForName:@"rel"] stringValue];
            NSString *type = [[link attributeForName:@"type"] stringValue]; 
            if ([rel compare:@"alternate"] == NSOrderedSame && 
                [type compare:@"text/html"] == NSOrderedSame) {
                articleUrl = [[link attributeForName:@"href"] stringValue];
            }
        }
        
        NSString *articleDateString = [item valueForChild:@"updated"];        
        NSDate *articleDate = nil;
        
        RSSEntry *entry = [[[RSSEntry alloc] initWithBlogTitle:blogTitle 
                                                  articleTitle:articleTitle 
                                                    articleUrl:articleUrl 
                                                   articleDate:articleDate] autorelease];
        [entries addObject:entry];
        
    }      
    
}

These functions are fairly simple, and pull out the data we’re looking for in each entry according to the RSS and Atom formats. If you’re unsure how this works, you might want to review the RSS and Atom formats, and take a look at the How To Read and Write XML Documents with GDataXML tutorial, which covers the API in more detail.

Note that right now the dates aren’t being parsed and are just being set to nil. We’ll get to that later.

Compile and run the code, and now you should see articles fly into the screen for all of the blogs!

RSS Feeds Parsed with GDataXML