In this post, I will continue to show you how to implement an rss reader iphone app in Xcode. First, I need to clarify that there is no different between iPhone app and iPad app, except the UI design. If you are working in Xcode 4.2 or later, you can design the UI in storyboard. It gives us a directly visual design experience. This is the 3rd tutorial for iPhone Rss Reader app development, you can find all tutorials about this topics:
- iPhone Rss Reader App IOS Tutorial 1: StoryBoard UI Programming
- iPhone Rss Reader App IOS Tutorial 2: HTTP Network Programming in IOS
- iPhone Rss Reader App IOS Tutorial 3: XML Parser in IOS
- iPhone Rss Reader App IOS Tutorial 4: Show Rss Feed HTML in UIWebView
In previous post, HTTP Network Programming in IOS, I give an example to show how to make HTTP GET request and HTTP POST request. Via HTTP connection, we can get the RSS Feed content in XML format. To show the title in our table view, we need to parse xml into our data structure first.
In IOS SDK, we can use NSXMLParser to handle all xml parsing jobs. The reason we choose XML instead of JSON is that RSS 2.0 feed is defined by XML. Here is a piece of RSS feed content example.
[xml]
http://jmsliu.com/1123/iphone-rss-reader-app-ios-tutorial-2-http-network-programming-in-ios.html
http://jmsliu.com/1123/iphone-rss-reader-app-ios-tutorial-2-http-network-programming-in-ios.html#comments
To create a complete rss reader iphone app, http request and response are both necessary modules. Website, for example, WordPress powered website, usually provides rss feed feature. Hence, using our rss reader iphone app can easily get the rss feed contents from Http request. HTTP network programming in IOS is very simple. NSURLConnection class and [...]
]]>
http://jmsliu.com/1123/iphone-rss-reader-app-ios-tutorial-2-http-network-programming-in-ios.html/feed
[/xml]
If you want to know what the whole RSS feed looks like, you can type following link in your browser to check:
http://jmsliu.com/feed
Using NSXMLParser to parse XML is very simple. As an example, I will create a class RssXMLParser to handle the xml parsing job. RssXMLParser will implement the delegate NSXMLParserDelegate. Hence, the RssXMLParser.h will look like:
[java]
#import
@interface RssXMLParser : NSObject
{
//for switch and case
enum nodes {title = 1, postlink = 2, pubDate = 3, invalidNode = -1};
enum nodes aNode;
//for holding the parsing result
NSMutableDictionary *articles;
//for matching the article title and link
NSString *lastTitle;
}
-(void) parseRssXML: (NSData *)xmldata;
@end
[/java]
In RssXMLParser.m, I will implement and override the following functions:
- - (void) parseRssXML:(NSData *)xmldata
- - (void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
- - (void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
- - (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
The function parseRssXML is defined by us, which will be called by view controller. The functions didStartElement, didEndElement, foundCharacters are defined by NSXMLParserDelegate. They will be called when NSXMLParser starts to parse the xml. The following is the example of RssXMLParser.m.
[java]
#import “RssXMLParser.h”
@implementation RssXMLParser
- (void) parseRssXML:(NSData *)xmldata
{
NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:xmldata];
[xmlParser setDelegate:self];
[xmlParser setShouldResolveExternalEntities:NO];
[xmlParser parse];
}
- (void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if([elementName isEqualToString:@"item"])
{
aNode = invalidNode;
if(articles == nil)
{
articles = [[NSMutableDictionary alloc] init];
}
}
else if([elementName isEqualToString:@"title"])
{
aNode = title;
}
else if([elementName isEqualToString:@"link"])
{
aNode = postlink;
}
else
{
aNode = invalidNode;
}
}
- (void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if( [elementName isEqualToString:@"rss"] )
{
NSLog(@”xxxxx”);
}
}
- (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
switch (aNode) {
case title:
{
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet nonBaseCharacterSet]];
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if(string.length != 0)
{
lastTitle = string;
}
}
break;
case postlink:
{
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet nonBaseCharacterSet]];
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if(string.length != 0 && articles != nil)
{
[articles setObject:string forKey:lastTitle];
}
}
break;
default:
break;
}
}
@end
[/java]
After NSXMLParser finished to parse the XML, the function didEndElement will be called. Now we have a new problem. Because parsing XML is unblocking process, it will process in different thread. Therefore, you don’t know when it will finish the whole parsing process after you call NSXMLParser parse. There are two ways to solve the problem.
- Implement the NSXMLParserDelegate in view controller
- Define a new delegate and let RssXMLParser call the delegate once it finished parsing job
In the first way, we implement the NSXMLParserDelegate in view controller. In our example, we can implement NSXMLParserDelegate in RssTableViewController. This way is much simple to code. However, it is not the best way in software design perspective. If we implement NSXMLParserDelegate in RssTableViewController, RssTableViewController will handle all xml parsing job, as well as view logical control. To separate the job responsibility, we choose RssXMLParser to manage the xml parsing logic. Therefore, the second way is much better.
In the second way, we define a delegate in RssXMLParser. In the delegate, there is only one function. We design RssXMLParser to call this function after it finishes the parsing. Hence, the new version of RssXMLParser.h will look like:
[java]
#import
@protocol XMLParserDelegate;
@interface RssXMLParser : NSObject
{
//for switch and case
enum nodes {title = 1, postlink = 2, pubDate = 3, invalidNode = -1};
enum nodes aNode;
//for holding the parsing result
NSMutableDictionary *articles;
//for matching the article title and link
NSString *lastTitle;
}
@property (assign, nonatomic) id
-(void) parseRssXML: (NSData *)xmldata;
@end
@protocol XMLParserDelegate
@required
- (void) onParserComplete: (NSObject *) data XMLParser: (RssXMLParser *) parser;
@end
[/java]
After we add the delegate variable in RssXMLParser.h, we can update the RssXMLParser.m as well.
[java]
@synthesize delegate;
- (void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if( [elementName isEqualToString:@"rss"] )
{
[delegate onParserComplete:articles XMLParser:self];
}
}
[/java]
Then, we can implement the XMLParserDelegate in the RssTableViewController. Here is RssTableViewController.h,
[java]
#import
#import “RssXMLParser.h”
@interface RssTableViewController : UITableViewController
{
NSMutableData *httpReceivedData;
}
@property (retain, nonatomic) NSMutableArray *rssData;
@end
[/java]
In RssTableViewController.m, we will update the function viewDidLoad, connectionDidFinishLoading and override function onParserComplete.
[java]
- (void)viewDidLoad
{
[super viewDidLoad];
rssData = [NSMutableArray array];
RssHttpController *httpController = [[RssHttpController alloc] init];
[httpController getRSSContent:[[NSNumber numberWithInt:1] stringValue] rssurl:@”http://jmsliu.com/feed?paged=” delegate:self];
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString *result = [[NSString alloc] initWithData:httpReceivedData encoding:NSUTF8StringEncoding];
NSLog(@”%@”, result);
RssXMLParser *rssXMLParser = [[RssXMLParser alloc] init];
[rssXMLParser setDelegate:self];
[rssXMLParser parseRssXML:httpReceivedData];
}
- (void) onParserComplete:(NSObject *)data XMLParser:(RssXMLParser *)parser
{
[parser setDelegate:nil];
articlesList = (NSMutableDictionary *)data;
NSArray *keyList = [articlesList allKeys];
for (NSString *title in keyList)
{
[rssData addObject:title];
}
[self.tableView reloadData];
}
[/java]
Now, let’s see the app result in my iphone.
Image may be NSFW.
Clik here to view.
Here are the first 5 articles in my website. To make the title nicer, we can do some simple customization in UITableViewCell. Here is a post to show you how to customize UI Table Cell View. After making some changes, the new UI looks like:
Image may be NSFW.
Clik here to view.
Add More Features in iPhone Rss Reader
After this tutorial, we already accomplish showing the website content in table view list. There are two more functions we have to add. Like other new reader app, pull down and refresh the content will be a necessary function. I recommend you to read another post: Pull Down to Refresh Tablet Content. It will show you how to add pull down to refresh component in this Rss Reader example.
Another feature we have to add is clicking the list item and show the real content in a web view. It will help users to read the details about the story, not just the title. In the next post, Show Rss Feed in UIWebView, I will add this feature in our iPhone Rss Reader example.