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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Rss Reader Tutorial for iOS: How To Make A Simple RSS Reader iPhone App
30 mins
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!
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:
- Go to to your RSSFun project directory in Finder, and make a new folder called ASIHTTPRequest, as a sibling to your Classes Folder.
- Now download the ASIHTTPRequest source.
- Untar the file to expand the source into a folder.
- Copy all of the files from the Classes directory that begin with ASI (but not the folders) to your RSSFun\ASIHTTPRequest folder.
- Also copy all of the files from the External\Reachability directory to your RSSFun\ASIHTTPRequest folder.
- 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.
- 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.
- 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.
- 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!
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:
- Go to to your RSSFun project directory in Finder, and make a new folder called GDataXML, as a sibling to your Classes Folder.
- Download the gdata-objective-c client library.
- Unzip the file, navigate to Source\XMLSupport, and copy the two files into your RSSFun\GDataXML folder.
- 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.
- 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.
- 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.
- Click Project\Edit Project Settings, go to the Build tab, and make sure “All Configurations” are checked.
- Find the Search Paths\Header Search Paths setting and add /usr/include/libxml2 to the list.
- 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!
Sorting by Date
Most of the time when you’re looking at RSS entries, you want to see them listed in the order of the date the articles were posted, so you make sure you’re reading recent articles. So let’s get that working in this iPhone app tutorial!
It turns out that converting date strings in RSS/Atom is non-trivial, due to the different formats dates can potentially be written in. Luckily, Michael Waterfall has written a helper class that solves the problem! In fact, he’s written an entire RSS feed parser, but we’re obviously not going to use that here since we’re taking the approach of writing it ourselves.
But we do need that date converting class of his. So add it to your project by taking the following steps:
- Download MWFeedParser
- Drag Classes\NSDate+InternetDateTime.h/m to your Classes folder. Make sure “Copy items into destination group’s folder (if needed)” is checked, and click Add.
Then switch to RootViewController.m and make the following changes:
// Add to top of file
#import "NSDate+InternetDateTime.h"
// Replace line that sets articleDate to nil in parseRss
NSDate *articleDate = [NSDate dateFromInternetDateTimeString:articleDateString formatHint:DateFormatHintRFC822];
// Replace line that sets articleDate to nil ni parseAtom
NSDate *articleDate = [NSDate dateFromInternetDateTimeString:articleDateString formatHint:DateFormatHintRFC3339];
Compile and run the code, and you’ll see that the dates show up OK now! Now all you need to do is sort them.
But how do you do that? I’m sure many of you are familiar with sorting arrays using methods such as sortedArrayUsingDescriptor or sortedArrayUsingComparator, but what should you do if you want to simply insert a new item into the appropriate spot for an already-sorted array?
Well, there’s no built-in way to do this with the iOS API, but we can add a helper method to NSArray to make this a lot easier. Basically, it will use binary searching to find the appropriate spot inside a sortes NSArray to insert a new element, based on a passed-in comparator block.
So let’s add this helper method by creating a category on NSArray. Select the Classes group, go to File\New File, choose iOS\Cocoa Touch Class\Objective-C class, and click Next. Name the class NSArray+Extras.m, make sure “Also create NSArray+Extras.h” is checked, and click Finish.
Replace NSArray+Extras.h with the following:
//
// Adapted from: http://blog.jayway.com/2009/03/28/adding-sorted-inserts-to-uimutablearray/
//
#import <Foundation/Foundation.h>
@interface NSArray (Extras)
typedef NSInteger (^compareBlock)(id a, id b);
-(NSUInteger)indexForInsertingObject:(id)anObject sortedUsingBlock:(compareBlock)compare;
@end
And NSArray+Extras.m with the following:
//
// Adapted from: http://blog.jayway.com/2009/03/28/adding-sorted-inserts-to-uimutablearray/
//
#import "NSArray+Extras.h"
@implementation NSArray (Extras)
-(NSUInteger)indexForInsertingObject:(id)anObject sortedUsingBlock:(compareBlock)compare {
NSUInteger index = 0;
NSUInteger topIndex = [self count];
while (index < topIndex) {
NSUInteger midIndex = (index + topIndex) / 2;
id testObject = [self objectAtIndex:midIndex];
if (compare(anObject, testObject) < 0) {
index = midIndex + 1;
} else {
topIndex = midIndex;
}
}
return index;
}
@end
If you read through indexForInsertingObject, you can see that it compares the current object to the middle of the array, and recurses in either the top half or bottom half, depending on the sort result. This is a fast way to find the appropriate spot to insert an object into a sorted array.
You'll see that we set up the method so you can pass in a block of code that performs the comparison as a parameter, to make it nice and easy to use as well.
So let's give it a try! Open RootViewController.m and make the following changes:
// Add to top of file
#import "NSArray+Extras.h"
// In requestFinished, replace the line that sets insertIdx to 0 with this
int insertIdx = [_allEntries indexForInsertingObject:entry sortedUsingBlock:^(id a, id b) {
RSSEntry *entry1 = (RSSEntry *) a;
RSSEntry *entry2 = (RSSEntry *) b;
return [entry1.articleDate compare:entry2.articleDate];
}];
Compile and run the code, and now the list of RSS entries should be sorted by date, and dynamically update as new articles come in!
Adding a Web View
Just one more finishing touch for this simple RSS reader - adding a web view to view a selected article! So let's quickly add this.
Select the Classes group in XCode, and go to File\New File, choose iOS\Cocoa Touch Class\UIViewController class, make sure "With XIB for User Interface" is checked but the others are not checked, and click Next. Name the class WebViewController.m, make sure "Also create WebViewController.h" is checked, and click Finish.
Open WebViewController.m and modify it to look like the following:
#import <UIKit/UIKit.h>
@class RSSEntry;
@interface WebViewController : UIViewController {
UIWebView *_webView;
RSSEntry *_entry;
}
@property (retain) IBOutlet UIWebView *webView;
@property (retain) RSSEntry *entry;
@end
This just adds an outlet for the web view we're about to add in Interface Builder, and a property to keep track of the RSS Entry to display.
Save your file. Then double click on WebViewController.xib and drag a web view from the library into the middle of the view so it fills up the whole area. Select the web view, and in the attributes inspector make sure "Scales pages to fit" is checked. Finally, control-click File's Owner, drag a line to the Web View, and connect it to the webView outlet.
Next make the following changes to WebViewController.m:
// At top of file
#import "RSSEntry.h"
// Under @implementation
@synthesize webView = _webView;
@synthesize entry = _entry;
// Add new methods
- (void)viewWillAppear:(BOOL)animated {
NSURL *url = [NSURL URLWithString:_entry.articleUrl];
[_webView loadRequest:[NSURLRequest requestWithURL:url]];
}
- (void)viewWillDisappear:(BOOL)animated {
[_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:blank"]]];
}
// Add inside dealloc
[_entry release];
_entry = nil;
[_webView release];
_webView = nil;
These methods simply display the appropriate URL in viewWillAppear, and clear the screen on viewWillDisappear.
Next add a new instance variable and property for the WebViewController in RootViewController.h:
// Before @interface
@class WebViewController;
// Inside @interface
WebViewController *_webViewController;
// After @interface
@property (retain) WebViewController *webViewController;
Then make the following changes to RootViewController.m:
// At top of file
#import "WebViewController.h"
// Under @implementation
@synthesize webViewController = _webViewController;
// Replace tableView:didSelectRowAtIndexPath with the following
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (_webViewController == nil) {
self.webViewController = [[[WebViewController alloc] initWithNibName:@"WebViewController" bundle:[NSBundle mainBundle]] autorelease];
}
RSSEntry *entry = [_allEntries objectAtIndex:indexPath.row];
_webViewController.entry = entry;
[self.navigationController pushViewController:_webViewController animated:YES];
}
// In didReceiveMemoryWarning
self.webViewController = nil;
// In dealloc
[_webViewController release];
_webViewController = nil;
Compile and run your code, and you should now be able to click a post to see an article!
Where To Go From Here?
Here is a sample project with all of the code we’ve developed in the above iPhone app tutorial.
I hope that this article was an interesting and practical example of reading and working with networked data in iOS, and hopefully covered some new and interesting things along the way! If you have any questions, thoughts, or suggestions for improvement, please let me know!
All videos. All books.
One low price.
A Kodeco subscription is the best way to learn and master mobile development — plans start at just $19.99/month! Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.
Learn more