I reviewing an old RSS reader project wherein the data is fetched from Apple press release website. The titles of the individual articles are to be the populated into the individual table cells controlled by a ListViewController. The Detail view (Web view in my case, controlled by WebViewController) will show the detailed article based on the selected title from the corresponding cell.
Here is the code:-
ListViewController.h
#import
@class RSSChannel, WebViewController, RSSContent;
@interface ListViewController : UITableViewController<nsxmlparserdelegate,uitableviewdelegate, uitableviewdatasource,="" nsurlconnectiondatadelegate="">
{
NSURLConnection *connection;
NSMutableData *xmlData;
RSSChannel *channel;
NSMutableArray *contentCollection;
}
@property (nonatomic, strong) WebViewController *webViewController;
@property RSSContent *content;
-(void)fetchEntries;
@end
ListViewController.m
//// ListViewController.m// Nerdfeed/// Created by Rahul Agarwal on 16/09/1// Copyright (c) 2014 Rahul Agarwal. All rights reserved.
#import "ListViewController.h"
#import "RSSContent.h"
#import "RSSChannel.h"
#import "RSSItem.h"
#import "WebViewController.h"
@interface ListViewController ()
@end
@implementation ListViewController
@synthesize webViewController, content;
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)io{
if ([[UIDevice currentDevice] userInterfaceIdiom]== UIUserInterfaceIdiomPad) {
return YES;
}
return io== UIInterfaceOrientationPortrait;
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
NSLog(@"ListViewcontroller init..%@", self.tableView.dataSource);
[self fetchEntries];
contentCollection= [[NSMutableArray alloc] init];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
-(void)fetchEntries{
NSLog(@"%@", NSStringFromSelector(_cmd));
xmlData= [[NSMutableData alloc] init];
NSURL *url= [NSURL URLWithString:@"https://www.apple.com/pr/feeds/pr.rss"];
NSURLRequest *req= [NSURLRequest requestWithURL:url];
connection= [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:YES];//self has been made NSURLConnection's delegate
}
-(void)connection:(NSURLConnection *)conn didReceiveData:(NSData *)data{
NSLog(@"%@", NSStringFromSelector(_cmd));
// Add the incoming chunk of data to the container we are keeping
// The data always comes in the correct order
[xmlData appendData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)conn{
NSLog(@"%@", NSStringFromSelector(_cmd));
// We are just checking to make sure we are getting the XML
NSString *xmlCheck= [[NSString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding];
// NSLog(@"xmlCheck= %@",xmlCheck);
NSXMLParser *parser=[[NSXMLParser alloc] initWithData:xmlData];
[parser setDelegate:self];
NSLog(@"parsing initiated");
[parser parse];
xmlData=nil;
connection=nil;
[self.tableView reloadData];
WSLog(@"channel test- %@\n %@\n %@\n",channel, [channel title], [channel infoString]);
}
-(void)connection:(NSURLConnection *)conn didFailWithError:(NSError *)error{
connection=nil;
xmlData=nil;
NSString *errorString= [NSString stringWithFormat:@"Fetch failed: %@",[error localizedDescription]];
UIAlertView *av= [[UIAlertView alloc] initWithTitle:@"Error" message:errorString delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
[av show];
}
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
NSLog(@"%@ found a %@ element", self, elementName);
if ([elementName isEqual:@"channel"]) { // element starts
// If the parser saw a channel, create a new object, have the ivar- 'channel' point to it.
channel= [[RSSChannel alloc] init];
// Give the channel object a pointer back to ourselves for later.
channel.parentParserDelegate= self;
// Set the parser's delegate to the channel object
// There will be a warning here, ignore it for now
parser.delegate= channel;
}
else if ([elementName isEqual:@"title"]) {
content= [[RSSContent alloc] init];
// Give the content object a pointer back to ourselves for later.
content.parentParserDelegate= self;
parser.delegate= content;
[contentCollection addObject:content];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{ // Return the number of sections.
return 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(@"no. of rows- %d", (int)contentCollection.count);
//#warning Incomplete method implementation.
// Return the number of rows in the section.
// return [[channel items] count];
return contentCollection.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"Cell no.- %d", (int)indexPath.row);
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell==nil) {
cell= [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
//
RSSContent *content= contentCollection[indexPath.row];
cell.textLabel.text= content.title;
NSLog(@"cell title- %@", cell.textLabel.text);
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
// Push the web view controller onto the navigation stack- this implicitly creates the web view controller's view the time through
NSLog(@"Tableview- %@ %@", self.tableView, tableView);
[[self navigationController] pushViewController:webViewController animated:YES];
// Grab the selected item
RSSItem *entry= [channel.items objectAtIndex:indexPath.row];
// Construct a URL with the link string of the selected item
NSURL *url= [NSURL URLWithString:entry.link];
// Construct a request-object with that URL
NSURLRequest *req= [NSURLRequest requestWithURL:url];
// Load the request into the webView
[webViewController.webView loadRequest:req];
// Set the title of the web view controller's navigation item
[[webViewController navigationItem] setTitle:entry.title];
}
@end
The above code basically performs the task of establishing connection, sending a URL request to the webserver, retrieving data in the form of XML, and having NSXMLParser delegate to the ListViewController the job of parsing that data tag by tag. The ListViewController then further divides the task of having NSXMLParser delegate the duty of parsing specific type of content, character by character, to RSSContent. In the interface file on the top, there are a couple of other forward declarations- RSSChannels, RSSItems. These are to be ignored for the purpose of this discussion.
Here is the code for RSSContent
RSSContent.h
#import
@interface RSSContent : NSObject
{
NSMutableString *currentString;
}
@property (nonatomic, weak)id parentParserDelegate;
@property (nonatomic, strong)NSString *title;
@end
RSSContent.m
#import "RSSContent.h"
@implementation RSSContent
@synthesize parentParserDelegate, title;
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
NSLog(@"parser did start element");
if ([elementName isEqual:@"![CDATA["]) {
currentString= [[NSMutableString alloc] init];
title= currentString;
}
}
-(void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock{
NSString *string= [[NSString alloc] initWithData:CDATABlock encoding:NSUTF8StringEncoding];
NSLog(@"\tfound CDATA within content- %@",string);
currentString= [[NSMutableString alloc] init];
title= currentString;
[currentString appendString:string];
}
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
NSLog(@"\tfound character(s) within content- %@",string);
[currentString appendString:string];
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
NSLog(@"\tcontent ended");
currentString= nil;
if ([elementName isEqual:@"title"]) {
parser.delegate= parentParserDelegate;
}
}
@end
In the code above, Im having RSSContent delegated the job of parsing the XML data , character by character, under the element name "title".
There is a back-pointer (parentParserDelegate) that RSSContent uses, to redirect the delegation flow to the ListViewController. The problem at this point is that, I'm getting blank cells when running the app. This is understandable since none of the UITableViewDataSource methods are getting called here. The reloadData message has been sent once, by one of the NSURLConnectionDataDelegate methods in the ListViewController.m file. What I cannot figure is when and where to send the reloadData message to the tableView. Pls advice. Any suggestions or tips given, could help me. Thank you