iPhone SDK Tutorial: Build a Simple RSS reader for the iPhone

259 Comments

With this I’m assuming you have a bit of familiarity with the iPhone SDK – you can download it for free from Apple’s site, and follow along here. We’re going to build an RSS feed reader for a simple feed (from The Apple Blog, no less).

Let’s get started

  1. Open Xcode and choose the “File” menu, in which you’ll click the “New Project…” item.
  2. Click “Application” under “iPhone OS” in the list at left.
  3. On the right, choose “Navigation-Based Application”. Then click the “Choose…” button. You’ll be prompted to pick a name and location. Type in the name “TAB RSS reader”.
  4. Save it wherever you wish.

The Xcode project window will appear, with the standard 3 panes – I recommend pulling the horizontal divider on the right side all the way to the top, since you’ll need that editor area and all the real estate you can give it.

Do you see a “Build and Go” button in the toolbar? Click it, or go to the “Build” menu, and click “Build and Go (Run)” there. It should open the Simulator application and launch a simple iPhone app that displays a blank navigation bar and blank table. Whee! Your first iPhone app. Now let’s sculpt it into something.

The project template that Apple provides has a lot of things already set up to get us started. On the list at the left of the project window, find “MainWindow.xib”, and double-click it. This is the basic framing of your application’s UI. Be careful not to mess around here too much. You just need to do one thing: you should see a “Navigation Controller” window with a basic interface mocked up – double-click on the navigation bar (which has no title in it), and type “The Apple Blog”. Press return. Save and quit Interface Builder.

Click once on “RootViewController.h” in the list, and see the code on the right. Make it look like this:


@interface RootViewController : UITableViewController {
	IBOutlet UITableView * newsTable;
	UIActivityIndicatorView * activityIndicator;
	CGSize cellSize;
	NSXMLParser * rssParser;
	NSMutableArray * stories;

	// a temporary item; added to the "stories" array one at a time, and cleared for the next one
	NSMutableDictionary * item;

	// it parses through the document, from top to bottom...
	// we collect and cache each sub-element value, and then save each item to our array.
	// we use these to track each current item, until it's ready to be added to the "stories" array
	NSString * currentElement;
	NSMutableString * currentTitle, * currentDate, * currentSummary, * currentLink;
}
@end

That’s the declaration file, where we’re telling the compiler what to expect when it runs through the controller logic. Here’s where the real work happens… Open “RootViewController.m“.

You’ll see that there’s more of the basic code to make that table view display – this controller is the table’s “delegate” – the table looks here to find out what it’s supposed to see/display/do in various situations, and sends calls for methods when the user performs various actions.

Change the value of - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section to return [stories count];

In our declarations, we told it we would have an array (NSMutableArray – a modifiable collection of objects), which we called “stories”. The [brackets] around that bit signify that it’s a message – we’re asking the stories array what its current count is – that is, how many items it has. Our RSS reader will grab as many items as it can (one for each story in the RSS feed), and place them in that array, so this method will tell the table This is how many rows we need: one for each item in the array, or for each item in the feed. Before, it was set to 0, so you’re giving it more information on our array.

Next up, modify the method below the one you just changed, like so:


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	static NSString *MyIdentifier = @"MyIdentifier";
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];

	if (cell == nil) {
		cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
	}

	// Set up the cell
	int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
	[cell setText:[[stories objectAtIndex: storyIndex] objectForKey: @"title"]];

	return cell;
}

As you can see, we used the “setText:” method to tell the cell what the contents will be. Each row in the table is basically a cell, and its properties are set in this method.

There are 4 methods highlighted in green about 3/4 of the way down – you can delete those if you wish, since we won’t be using them. They have to do with adding/deleting items.

If you were to run the program again now, it still wouldn’t do anything: we haven’t added the ability to download the feed and use it yet, so let’s do that now.

Edit the “viewDidAppear:” method to look like this:


- (void)viewDidAppear:(BOOL)animated {
	[super viewDidAppear:animated];

	if ([stories count] == 0) {
		NSString * path = @"http://feeds.feedburner.com/TheAppleBlog";
		[self parseXMLFileAtURL:path];
	}

	cellSize = CGSizeMake([newsTable bounds].size.width, 60);
}

This is where we tell the parser which feed to download. It calls a method, which you’ll want to paste in now:


- (void)parseXMLFileAtURL:(NSString *)URL {
	stories = [[NSMutableArray alloc] init];

	//you must then convert the path to a proper NSURL or it won't work
	NSURL *xmlURL = [NSURL URLWithString:URL];

	// here, for some reason you have to use NSClassFromString when trying to alloc NSXMLParser, otherwise you will get an object not found error
	// this may be necessary only for the toolchain
	rssParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];

	// Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks.
	[rssParser setDelegate:self];

	// Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
	[rssParser setShouldProcessNamespaces:NO];
	[rssParser setShouldReportNamespacePrefixes:NO];
	[rssParser setShouldResolveExternalEntities:NO];

	[rssParser parse];
}

This is a method we’ve added that creates the empty array for stories, creates a parser, and starts downloading the feed. As the parser works, this controller we’re working in will receive the various delegate methods, which you can paste in now:


- (void)parserDidStartDocument:(NSXMLParser *)parser {
	NSLog(@"found file and started parsing");
}

- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
	NSString * errorString = [NSString stringWithFormat:@"Unable to download story feed from web site (Error code %i )", [parseError code]];
	NSLog(@"error parsing XML: %@", errorString);

	UIAlertView * errorAlert = [[UIAlertView alloc] initWithTitle:@"Error loading content" message:errorString delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
	[errorAlert show];
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
	//NSLog(@"found this element: %@", elementName);
	currentElement = [elementName copy];

	if ([elementName isEqualToString:@"item"]) {
		// clear out our story item caches...
		item = [[NSMutableDictionary alloc] init];
		currentTitle = [[NSMutableString alloc] init];
		currentDate = [[NSMutableString alloc] init];
		currentSummary = [[NSMutableString alloc] init];
		currentLink = [[NSMutableString alloc] init];
	}
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{

	//NSLog(@"ended element: %@", elementName);
	if ([elementName isEqualToString:@"item"]) {
		// save values to an item, then store that item into the array...
		[item setObject:currentTitle forKey:@"title"];
		[item setObject:currentLink forKey:@"link"];
		[item setObject:currentSummary forKey:@"summary"];
		[item setObject:currentDate forKey:@"date"];

		[stories addObject:[item copy]];
		NSLog(@"adding story: %@", currentTitle);
	}
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
	//NSLog(@"found characters: %@", string);
	// save the characters for the current item...
	if ([currentElement isEqualToString:@"title"]) {
		[currentTitle appendString:string];
	} else if ([currentElement isEqualToString:@"link"]) {
		[currentLink appendString:string];
	} else if ([currentElement isEqualToString:@"description"]) {
		[currentSummary appendString:string];
	} else if ([currentElement isEqualToString:@"pubDate"]) {
		[currentDate appendString:string];
	}
}

- (void)parserDidEndDocument:(NSXMLParser *)parser {

	[activityIndicator stopAnimating];
	[activityIndicator removeFromSuperview];

	NSLog(@"all done!");
	NSLog(@"stories array has %d items", [stories count]);
	[newsTable reloadData];
}

Unfortunately, the NSXMLParser is the only simple XML-parsing tool available on iPhone (some of my favorites from the Mac are missing). So, this means we have to crunch through the file in order from top to bottom. We have a series of strings that we assign values to, and then collect them into story items, which are saved one by one. Once it hits the closing “item” tag, it saves that story, clears out the fields, and starts on the next item until we reach the end of the file. Not my favorite approach, but it works.

Finishing up

We need to shut off any potential memory leaks (it’s a good habit to get into, when you don’t have garbage collection – who needs that anyway?). Drop in this change:


- (void)dealloc {
	[currentElement release];
	[rssParser release];
	[stories release];
	[item release];
	[currentTitle release];
	[currentDate release];
	[currentSummary release];
	[currentLink release];

	[super dealloc];
}

Next, open up “RootViewController.xib“, and hold down the “control” key on your keyboard, while dragging from the “RootViewController” cube icon over to the table view, and release. You should see a list of three items appear, so click on the “newsTable” item. Save and quit Interface Builder.

Build and Go

If you click “Build and Go”, you’ll see the results we have so far. If you were to run this on an actual iPhone and not in simulator, the results would be different slightly: the hardware is slower, and if you’re on EDGE, the RSS feed will take a very long time to download. But, hey, it works! One thing that doesn’t work yet: when you tap on an item in the table, nothing happens. This is default behavior, but let’s make the stories open in Safari – that’s an easy thing to do. Just change this method:


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
	// Navigation logic

	int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];

	NSString * storyLink = [[stories objectAtIndex: storyIndex] objectForKey: @"link"];

	// clean up the link - get rid of spaces, returns, and tabs...
	storyLink = [storyLink stringByReplacingOccurrencesOfString:@" " withString:@""];
	storyLink = [storyLink stringByReplacingOccurrencesOfString:@"n" withString:@""];
	storyLink = [storyLink stringByReplacingOccurrencesOfString:@"	" withString:@""];

	NSLog(@"link: %@", storyLink);
	// open in Safari
	[[UIApplication sharedApplication] openURL:[NSURL URLWithString:storyLink]];
}

Now, click “Build and Go” again, to see that it works.

Done for Now


You can download the finished project file here if you wish.

Check back here later, and we’ll cover some steps on how to clean up the UI, and add some navigation.

259 Comments

James

Very nice tutorial. I’m very much looking forward to seeing how to implement navigation into the next view (instead of launching safari.)

Thanks again!

Jon

@Geuis – To provision your iPhone for development, connect your iPhone to your machine and fire up Xcode. From the Window menu, select “Organizer”. You should see your iPhone in the list of devices. Click on the iPhone in that list, and I suspect you’ll be able to figure out the rest from there… =)

Cheers,
Jon

oregon_tony

I’ll chip in a few bucks for your legal defense if needed. Maybe you’ll get 15 minutes of fame from it. More examples like this are needed in order to help developers not familiar with Xcode overcome the initial learning curve of building an application.

It seems like the current SDK restrictions will also prevent 3rd party books on iPhone development from being published. If you look on Amazon under “iPhone Development” there are several titles in the works, but at the moment it doesn’t seem like they could ever be legally published.

James Frost

@Vincent: Also, to response to your questions:

Square brackets are used in message sending (calling methods) – [foo baz]; sends the message baz to foo (calls the method baz on the object foo. [foo fireEmployee:@”Bob” withReason:@”None needed”]; would call the fireEmployee:withReason method on foo.

– is used infront of method declarations to denote an instance method. + denotes a class method.

James Frost

@Vincent (#19): Are you essentially saying that Objective-C’s syntax is horrible because it’s *shock* different to things you’ve used before? Perhaps, before slagging off a language for being ‘awful’, you should learn why it’s different, so you can make an informed opinion. I was unsure of Ruby’s syntax when I first saw it (Python being my previous favourite) but I looked into it, took the time to learn some things, and saw that many of its conventions made a lot of sense.

Objective-C is a fantastic language – I suggest you start with http://cocoadevcentral.com/d/learn_objectivec/ to see if it’s something you could get along with.

Jason Terhorst

Yes, you’d need a developer account to create provisions for your device.

But we don’t discuss that here. Definitely NDA, with good reason. ;)

@Dan: Yes, that book is a definite recommendation for getting started. This tutorial is meant for those with experience in Objective-C, or those clever enough to work their way through it, with the assumption that it will pique your interest enough that you’ll go out and find materials to get up to speed and figure other things out. :) Personally, I learn best by taking apart existing working code and understanding it that way.

Geuis

@Michael N Yeah I was coming to that realization after finally getting the app running last night. I’m still confused though, because when I first opened Xcoode and had my phone plugged in, it asked me if I wanted to use the phone as a development device. I clicked no the first couple times, but now I can’t get that prompt to appear again. Even if I can’t deploy to the phone right now, I don’t want to be in a situation later on where I can’t deploy even after I pay the blood toll to Apple.

Michael N

@Geuis It does’t really matter that you can’t set it to use your iPhone, you need to register with Apple and pay the $100 to get a key (or what ever it’s called) that will allow you to both run your app on your iPhone and submit it to the App store. With out that key all you can do is test on the simulator.

If anyone knows differently then I would love to hear from them.

Volure

look into cydia, jailbreak and the ldid command line util.

Youll be glad you did.

MobileMii

Ohw. I see no edit button so:
If I do the ctrl drop I see only tableview and view.

MobileMii

Nice, but I’m stuck at the part when you have to ctrl drop the box to table view.
I’m a Super-newbie, so can somebody help me???

Volure

control click is mac’s version of right click. You can hold control then click and drag from control to class object. or you can use the right click….

And remember. Drag from object to code to send data from Interface Builder (or to make interface Builder Get data from code). and Drag from code to object to Get data from Interface Builder (such as letting an IBOutlet access a control on the interface).

Dan Greenblatt

Thanks for this excellent tutorial. I have a tiny bit of Objective-C programming background, but haven’t used it in about a year. I’ve been wanting to pick up iPhone programming but have been mortified to climb back up that steep Objective-C learning curve. This article was a great (re-)introduction, and I found your small descriptions of each chunk of code very useful in reorienting myself to the disorienting world of Objective-C :)

It might be a good thing to add in the beginning that this article assumes some familiarity with Objective-C (in addition to the iPhone SDK, as you already point out). To some of the commenters points, this is *not* an Objective-C tutorial nor does it purport to be. IMHO, for a great introduction to (and in-depth coverage of) ObjC, check out Aaron Hillegass’ “Cocoa Programming for Mac OS X” — it’s not specificially about iPhone but it will get you on the right track ….

Vincent

I have never programmed in Objective-C but I must say the syntax looks absolutely horrendous. I have a strong CS background including a lot of C++, but I cannot understand half of the constructs at first sight. The use of () [] is uberly inconsistent. And why does it requires a dash before every method declaration? Why not use the same syntax as C/C++ as C# did? I can’t believe Mac developers are forced to write application in that awful language.

They must feel very 1980 when they see neat programs written in a modern language.

Volure

Im a C# programmer myself and I originally hated obj-c. Now I find obj-c to be a simple and elegant language.

the brackets are used for objects. and if you feel like it you can code in c. but why ? each call to property or function are fairly strait forward. Your issue was probably the same as mine. I wanted it to be too complicated and saw it as such. Its so absurdly simple that it took me a long time to grasp it. (think mac…. Think Simple)

larry

Great all these dudes will follow this and we will get another 400 useless apps that crash our phones!

Geuis

When I first plugged my iPhone in, Xcode asked a couple of times if I wanted to use it as a development device. Now that I have the sample app running in the simulator, when I connect the phone I no longer get the prompt to let Xcode use it. I went through Preferences but I don’t see any options to make Xcode redetect the phone.

Ned Ludd

How is this a tutorial? Just copy and paste some pre-written code into your IDE? Doesn’t really help anyone understand how to actually code for the iPhone.

Volure

Dude. Your an idiot if you cant get a tutorial out of this code.

Michael N.

@Jason: I thought as much, so I added the “” to my code right after I left that comment.

Looking forward to your update to this tutorial, any chance you’ll add the ability to view the stories in the app rather than opening Safari?

Jason Terhorst

@jtbandes: NSXMLParser is one of the few that *is* available on the iPhone OS, on the device. I’m not sure what you’re talking about, but I have a couple shipping apps that use NSXMLParser for key functions.

@Michael N.: I made a bit of a typo. Where you see @”n”, it should actually be @”n” (a newline character)… sorry about that. :) It’s not needed, but in some of my clients’ RSS feeds, there are odd spaces and line breaks, so that helps keep the iPhone from choking on it, so I’ve made it a bit of habit.

Michael N.

Great tutorial, thanks Jason.

The only issue I had was that the links opened to the wrong website. The line:

storyLink = [storyLink stringByReplacingOccurrencesOfString:@”n” withString:@””];

in the last part is removing the n in http://www.feedburner.com so all links open to some spam site named http://feedburer.com/. I commented out this line and it worked fine.

Zed

Can someone clarify jtbandes claim that NSXML is not supported on iPhone? I Googled and found a lot of stuff saying it wasn’t supported but would compile in the simulator as jtbandes claims. I did find one forum post that says it is supported on the phone itself. Would you care to comment Jason?

To Ig: He specifically cleans up memory so what are you talking about? Care to cite an example and improve this article for everyone? Is your point to be helpful or just rude?

Thanks very much for taking the time to write this article. I really appreciate it.

lg

looks to me like there are a lot of memory leaks in that code…

Ross Youngblood

Specify line numbers if you have something… otherwise it’s not helpfull.(going only halfway.)

jtbandes

Nice tutorial. Only one problem — NSXML* isn’t available on the iPhone… it’s in the Simulator SDK, though, which is why you can compile it. I suggest using TouchXML; the usage (for non-extravagant uses of NSXML*) is exactly the same, unless your XML document has namespaces, in which case it’s a simple adjustment.

Rishi

Dude…SDK violation. Prepare for Apple’s legal team! But, great job! This is amazing. Thanks for ignoring Apple’s idiotic NDA and writing what people have been looking for since the SDK came out.

Martin

Why would apple not want resources published that help people build applications for their phone?

Per Ejeklint

Very nice! Thank’s for your tutorial, much appreciated.

Stepan Mazurov

Great tutorial, thanks a lot for putting this out, saved on my harddrive ready to be reposted in case apple watchdogs get to you.

Joseph

I have been critical of TAB as of late, but kudos to you for telling Apple where it can shove its NDA. Well done!

Jon

The NDA is ludicrous, and for beginners like myself, this sort of tutorial is an absolute pile of GOLD!! THANK YOU, THANK YOU, THANK YOU!!!! =D

larry

You may have just violated the NDA! The Apple police will be circling your home about now!

Comments are closed.