iPhone App displaying result of web data model Lesson 5B. This is the second part of the lesson to look at how to create a data model to sit behind the table created in lesson 4. In this part the data within the model will be extracted from the web.

Now we have our data model let us put some data into it that is a little more meaningful. We will utilise the RSS feed from a news site to populate our model and hence our view.

First of all let us consider the two string variables we created in our model within lesson 5(A) to represent the summary and detail data. If we are to store and display some data from the RSS feeds it may be more appropriate to rename these variables to be title and description. XCode provides an easy mechanism to do this. By selecting Refactor from the Edit menu within XCode you can change the name of a variable or method throughout the code.

It may also help you to rename your methods for getSummary and getDetail as well but this is all down to your personal preference.

In order to accept the RSS data we need to create a method to get the news items. We will also need to manipulate or parse the received data as well to break it down into the fields that we want, namely the title and description to start with.

To help with the navigation of the code it is a good idea to create the reader and parser files under a different group. To create a new group just right click the project name and Add &gt New Group. Within this group Add a new Objective-C class file for the RssReader.

Rss Reader

To put the RSS data into our model we need a variable that represents an instance of our model and one that represents the Received data.

@interface RssReader : NSObject
{
NSMutableData *receivedData;
DataModel* model;
}

@property(retain) NSMutableData *receivedData;
@property(assign) DataModel* model;

@end

Within our code we need to initialise the received data. We don’t know how much data is going to be received with each feed so we initialise it with a size suitable to accept a reasonable amount of data. If the data received is larger than our guess (30K) the array will grow but become less efficient. We have to balance memory usage against speed.

– (id) init
{
self = [super init];
if (self != nil)
{
self.receivedData = [[NSMutableData alloc] initWithLength:30000];
}
return self;
}

As stated earlier we need a method to get the news items by taking in the RSS feed. First we need to perform a URL request like we did in lesson 3. However, this time instead of calling the URL request with a users input string, we will use a feed. The feed will be generated when we load the view within the RootViewController, but we will cover that in a moment.

-(void) getNewsItems:(NSURL *)feed
{
NSURLRequest* req = [[NSURLRequest alloc] initWithURL:feed];

When the App is running and we are processing the news items we still want the user to be able to see the news already received and utilise any UI widgets within the App. Therefore we need to be able to perform an asynchronous load of the RSS feed. To do this we use the NSURLConnection class — this class manages the http communication required to retrieve the rss data.

theConnection = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:YES];

Remember to declare the theConnection variable within your RSSReader.h file.

NSURLConnection* theConnection;

It does not need to be accessed outside our class so no @property or @synthesize is required. The NSURLConnection passes information back using callbacks on its delegate, the current class (delegate:self)in this instance. Look at the online documentation to determine what they are and how to fulfil them within your RSSReader code. Here are some hints.

Within the connection didReceiveResponse we need to make sure we clear any previous ReceivedData

– (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// Starting a new response so clear data
[
self.receivedData setLength:0];
}

Within the connection didReceiveData we need to make sure we append the new data to any existing data received.

– (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// append the new data to the receivedData
// receivedData is declared as a method instance elsewhere
[
self.receivedData appendData:data];
}

It is also good practice to create some data within the log in the event of connection failure to allow you to see the failing URL.

– (void)connection:(NSURLConnection *)connection
didFailWithError:(
NSError *)error
{
// inform the user
NSLog(@”Connection failed! Error – %@ %@”,
[error
localizedDescription],
[[error
userInfo] objectForKey:NSErrorFailingURLStringKey]);

[connection release];
}

When the data has finished loading
connectionDidFinishLoading is called. We need to parse the data to extract the elements we want to display. For this we need to create our RSS Parser methods so Add a new Objective-C class file RSSParser. We will add a method parseRssFeed to parse the data received via the URL connection.

Using the RSSParser

We can utilise the parser method within our RSSReader code.

– (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//Parse the received data
[
rssParser parseRssFeed:self.receivedData];
[connection
release]; //Clean up
}


RSS Parser

To parse our data we will use the delegate method for the NSXMLParser class. This class in a similar fashion to NSURLConnection uses a delegate to perform operations on its data. Again check out the online documentation to determine the methods within this class.

First we need to initialise the parser with the data from the RSS feed. This will be passed into our method when we call it from our RSSReader.

-(void) parseRssFeed:(NSData *)rssData
{
NSXMLParser* parser = [[NSXMLParser alloc] initWithData:rssData];

Then we need to set the delegate method and parse the data.

[parser setDelegate:self]; //Tell the parser which object to call back to with parsed data
[parser
parse]; //Now parse it

Once we have finished parsing the data and filling up our data model we need to inform ‘others’ we have finished. We do this using the NSNotification to ‘broadcast’ a DataUpdateNotification message to whoever is interested in listening. This is a very useful and powerful way to ensure we loosely couple components of our design from one to another.

[[
NSNotificationCenter defaultCenter] postNotificationName:DataUpdatNotification object:nil];

We listen for the signal in our RootViewController class. In the method viewDidLoad we add the following:-


[[
NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(dataChanged🙂
name:DataUpdatNotification object:nil];

This tells the notification center to call the method dataChanged on the current class whenever a DataUpdateNotification message is broadcast.

-(void)dataChanged:(id) info
{
[
self.tableView reloadData];
}

In this method we simply refresh the table view with the downloaded content.

Within the Parser the delegate methods we are concerned with are parser:didStartElement, didEndElement and foundCharacters. But first let us look at the RSS data format.

rssfeed

Within didStartElement we need to check for each new &ltitem&gt and create a new item in our DataItems array.

– (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
isAnElement = false;

if ([elementName isEqualToString:@”item”])
{
isItem = true;
//Create a new item in the array
dataItem = [[DataItems alloc] init];
}

We create the variables isAnElement and isItem so we know when we are processing items and elements. Only when we are processing an item do we then process an element.

i.e.
&lt item &gt
&lt title &gt hello I’m an element &lt /title &gt
//We process this
&lt /item &gt

&lt someOtherStuff &gt blah blah blah &lt /someOtherStuff &gt //We ignore this as it is not inside an item

&lt item &gt
&lt description &gt hello I’m another element &lt /description &gt
//We process this
&lt /item &gt

if (isItem)
{
if ([elementName isEqualToString:@”title”])
{
isAnElement = true;
self.currentElement = [NSMutableString string];
}

if
([elementName isEqualToString:@”description”])
{
isAnElement = true;
self.currentElement = [NSMutableString string];
}
}

When processing the XML the parser calls foundCharcters repeatedly. If we are currently within an element we append the received characters to the currentElement string.

– (
void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if (isAnElement)
{
[
self.currentElement appendString:string];
}
}

The NSXMLParser calls didEndElement for each &ltitem or Element&gt that it finds.

For each &lt/item&gt we get to we know that we have processed all the data for that item so we can now add the finished item to our data model.

– (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if (isItem)
{
if ([elementName isEqualToString:@”item”])
{
isItem = false;
isAnElement = false;
NSLog(@”New item”);
//Append new element to the data model
[
self.model addElement:dataItem];
}

For each &lt/Element&gt e,g &lt/title&gt or &lt/description&gt we know we have all the data required so we set the individual data Item element e.g. dataItem.title = self.currentElement, i.e. set it to the contents we had built up with a series of calls to foundCharacters.

if([elementName isEqualToString:@”title”])
{
dataItem.title = self.currentElement;
NSLog(@”title: %@”,self.currentElement);
isAnElement = false;
}
if([elementName isEqualToString:@”description”])
{

NSLog(@”description: %@”,self.currentElement);
dataItem.desc = self.currentElement;
isAnElement = false;
}

Using the RSSReader

All that is left to do now is set up your news feed and apply the RSSReader to the feed. As stated earlier in this lesson this should be done as part of viewDidLoad once the data model has been initialised.

NSURL* feed = [[NSURL alloc] initWithString: @”http://newsrss.bbc.co.uk/rss/newsplayer_uk_edition/sci-tech/rss.xml”];

rssReader = [[RssReader alloc] init];
rssReader.model = model;
[
rssReader getNewsItems:feed];

N.B. For the purposes of this lesson we chose the BBC science news stories but obviously all of the code we have produced will work on any RSS feed with just a change in the parser elements if different or more elements are passed in the feed.

Although this has been a long and quite intense lesson performing a Build and Run at various points during your coding should ensure you maintain consistency on your variable names, and that you have correctly declared variables and imported headers where necessary. Remember to release any memory as required at the end of your classes, (e.g. self.currentElement within your parser code) to ensure you don’t leave any leaks in the code.

Here are some screen shots of our finished App.

Screen shot 2009-11-06 at 11.32.43

Screen shot 2009-11-06 at 11.32.50

Next Tutorial

When you have completed this tutorial why not spend some time parsing the other elements within the RSS feed and displaying them appropriately in your view.

In the next tutorial we will look at adding some general graphics and further navigation to the App.