tableview being unable to reload data

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

Accepted Reply

You might try returning 1 from numberOfSections. Returning 0 tells the table view there’s no data to display.

Replies

Is the NSURLConnection delegate callback coming on a background thread? If so, you must use dispatch_async to call reloadData on the main thread.

Pretty basic point, so you probably did it already: have you checked dataSource and delegate are set for the tableView ?

My dataSource is _UIFilteredDataSource, and delegate is ListViewController. Nowhere have I explicitly had the two pointers point to ListViewController, though.

You can do it in IB (connect tableView to its ListViewController viewController) or in code

myTableView.dataSource = self
myTableView.delegate = self

No, its running on the main thread already.

Before posting my issue, I had tried running the code after having done what you showed in your code snippet. Still no change. Out of curiosity though, does one have to explicitly hookup the two tableView pointers to the viewcontrollers? Because, for some strange reason, the delegate does come up as viewcontroller after i NSLogged to the console, this-

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
        NSLog(@"ListViewcontroller init..%@ %@", self.tableView.dataSource, self.tableView.delegate);
        
        [self fetchEntries];
        contentCollection= [[NSMutableArray alloc] init];
    }
    return self;
}


Console output-

2018-11-13 16:00:49.609 Nerdfeed[3312:302465] ListViewcontroller init..<_UIFilteredDataSource: 0x61000004c0f0> <ListViewController: 0x7f82eb7053e0>

And this is without me having done any explicit prior assignment like you have in your example. And, im not using any IB file for designing a custom tableview. The tableview being used here is the one provided to me by default due to the fact that my ListViewController happens to be a tableviewcontroller.

You might try returning 1 from numberOfSections. Returning 0 tells the table view there’s no data to display.

You know what? You hit the nail on its head. I'd had the numberOfSections as 0 all along. How foolish of me to not notice that. Thanks so much for the assist, sir. Now i can see the cells populated with the relevant information.

Just one more thing. Was it ok to have connectionDidFinishLoading: method send the reloadData message to the tableView or could there have been a better way to have the table info reloaded, in that having reloadData sent from some other method? Pls suggest some alternatives