The WebService challenge... (proposal)


#1

Hi,

I tried to make the challenge for this chapter.
I have a lot of doubts on several parts (especially the release pointers and the xmlParser).
The “detail song” is a view that contains a tableView with 4 rows (Album,Artist name, Genre and Price).

The code works but I would like know how to optimize it…

I thank you in advance for your help

Regards,

Stan

Song.h

#import <Foundation/Foundation.h>
@interface Song : NSObject {
	NSString *album;
	NSString *artist;
	NSString *genre;
	NSString *prix;
	NSString *imageUrl;
}
@property (nonatomic,copy) NSString *album;
@property (nonatomic,copy) NSString *artist;
@property (nonatomic,copy) NSString *genre;
@property (nonatomic,copy) NSString *prix;
@property (nonatomic,copy) NSString *imageUrl;
@end

Song.m

#import "Song.h"
@implementation Song
@synthesize album, artist,genre, prix,imageUrl;
-(void) dealloc
{
	[album release];
	[artist release];
	[genre release];
	[prix release];
	[imageUrl release];
	[super dealloc];
}
@end

RSSTableViewController.h

#import <UIKit/UIKit.h>
@class Song;
@class SongDetailViewController;
@interface RSSTableViewController : UITableViewController {
	SongDetailViewController *detailSongViewController;
	BOOL waitingForEntryTitle;
	NSMutableString *albumString;
	NSMutableString *artistString;
	NSMutableString *genreString;
	NSMutableString *prixString;
	NSMutableString *imageUrlString;
	Song *aSong;
	NSMutableArray *songs;
	NSMutableData *xmlData;
	NSURLConnection *connectionInProgress;
	NSMutableString *currentElementValue;
}
@property (nonatomic, retain) NSMutableArray *songs;
-(void) loadSongs;
@end

RSSTableViewController.m

#import "RSSTableViewController.h"
#import "Song.h"
#import "SongDetailViewController.h"
@implementation RSSTableViewController
@synthesize songs;
- (id)initWithStyle:(UITableViewStyle)style
{
	if(self=[super initWithStyle:style])
	{
		songs=[[NSMutableArray alloc] init];
	}
	[[self navigationItem] setTitle:@"Top Songs"];
	return self;
}
-(void)viewWillAppear:(BOOL)animated
{
	[super viewWillAppear];
	[self loadSongs];
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
	if (!detailSongViewController) {
		detailSongViewController=[[SongDetailViewController alloc] init];
	}
	[detailSongViewController setEditingSong:[songs objectAtIndex:[indexPath row]]];	
	[[self navigationController] pushViewController:detailSongViewController animated:YES];
}
-(void)loadSongs
{
	[songs removeAllObjects];
	[[self tableView] reloadData];
	NSURL *url=[NSURL URLWithString:@"http://ax.itunes.apple.com/"
				@"WebObjects/MZStoreServices.woa/ws/RSS/topsongs/"
				@"limit=10/xml"];
	NSURLRequest *request=[NSURLRequest requestWithURL:url
										   cachePolicy:NSURLRequestReloadIgnoringCacheData
									   timeoutInterval:30];
	if (connectionInProgress)
	{
		[connectionInProgress cancel];
		[connectionInProgress release];
	}
	[xmlData release];
	xmlData =[[NSMutableData alloc] init];
	connectionInProgress=[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}
- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}
- (void)dealloc {
	[detailSongViewController release];
	[songs release];
    [super dealloc];
}


#pragma mark DataSource
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger) section
{
	return [songs count];

}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
	UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];
	if (cell==nil)
	{
		cell=[[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"UITableViewCell"] autorelease];
	
	}
	Song *s =[songs objectAtIndex:[indexPath row]];
	[[cell textLabel] setText:[s album]];
	return cell;
}

#pragma mark WebServices Delegates

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
	[xmlData appendData:data];
}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
	NSXMLParser *parser=[[NSXMLParser alloc] initWithData:xmlData];
	[parser setDelegate:self];
	[parser parse];
	[parser release];
	[[self tableView] reloadData];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
	[connectionInProgress release];
	connectionInProgress=nil;
	[xmlData release];
	xmlData=nil;
	NSString *errorString=[NSString stringWithFormat:@"Fetch failed: %@",[error localizedDescription]];
	UIActionSheet *actionSheet=[[UIActionSheet alloc] initWithTitle:errorString delegate:nil 
												  cancelButtonTitle:@"OK"
											 destructiveButtonTitle:nil 
												  otherButtonTitles:nil];
	[actionSheet showInView:[[self view] window]];
	[actionSheet autorelease];
}

#pragma mark XMLParse Delegates
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 
attributes:(NSDictionary *)attributeDict
{
	[currentElementValue release];
	currentElementValue = nil;
	if ([elementName isEqual:@"entry"])
	{
		waitingForEntryTitle=YES;
	}
	if ([elementName isEqual:@"im:name"] && waitingForEntryTitle)
	{
		currentElementValue = [[NSMutableString alloc] init];
		albumString =[[NSMutableString alloc] init];
	}
	if ([elementName isEqual:@"im:artist"] && waitingForEntryTitle)
	{
		currentElementValue = [[NSMutableString alloc] init];
		artistString =[[NSMutableString alloc] init];
	}	
	
	if ([elementName isEqual:@"im:price"] && waitingForEntryTitle)
	{
		currentElementValue = [[NSMutableString alloc] init];
		prixString =[[NSMutableString alloc] init];
	}	
	if ([elementName isEqual:@"im:image"] && waitingForEntryTitle)
	{
		if ([[attributeDict objectForKey:@"height"] intValue]==60) 
		{
		currentElementValue = [[NSMutableString alloc] init];
		imageUrlString =[[NSMutableString alloc] init];
		}
	}	
	if ([elementName isEqual:@"category"] && waitingForEntryTitle)
	{
		genreString=[[NSMutableString alloc] init];
		[genreString setString:[attributeDict objectForKey:@"label"]];
	}	
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 
{
	if ([elementName isEqual:@"im:name"] && waitingForEntryTitle)
	{
		[albumString setString:currentElementValue];
		[currentElementValue release];
		currentElementValue = nil;		
	}
	if ([elementName isEqual:@"im:artist"] && waitingForEntryTitle)
	{
		[artistString setString:currentElementValue];
		[currentElementValue release];
		currentElementValue = nil;		
	}
	if ([elementName isEqual:@"im:price"] && waitingForEntryTitle)
	{
		[prixString setString:currentElementValue];
		[currentElementValue release];
		currentElementValue = nil;		
	}
	if ([elementName isEqual:@"im:image"] && currentElementValue!=nil && imageUrlString!=nil && waitingForEntryTitle)
	{
		[imageUrlString setString:currentElementValue];
		[currentElementValue release];
		currentElementValue = nil;		
	}
	if ([elementName isEqual:@"entry"])
	{
		aSong =[[Song alloc]init];
		[aSong setAlbum:albumString];
		[aSong setArtist:artistString];
		[aSong setPrix:prixString];
		[aSong setImageUrl:imageUrlString];
		[aSong setGenre:genreString];
		[songs addObject:aSong];
		[aSong release];
		aSong=nil;
		[albumString release];
		[artistString release];
		[prixString release];
		[imageUrlString release];
		[genreString release];
		albumString=nil;
		artistString=nil;
		prixString=nil;
		imageUrlString=nil;
		genreString=nil;
		waitingForEntryTitle=NO;
		[currentElementValue release];
		currentElementValue = nil;		
	}	
}
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
	if(currentElementValue)
	{
		[currentElementValue appendString:string];
	}
}
@end

SongDetailViewController.h

#import <UIKit/UIKit.h>
@class Song;
@interface SongDetailViewController : UIViewController {
	IBOutlet UITableView *tableView;
    Song *editingSong;
}
- (void)setEditingSong:(Song *)song;
@end

SongDetailViewController.m

#import "SongDetailViewController.h"
#import "Song.h";
@implementation SongDetailViewController
-(void)viewWillAppear:(BOOL)animated
{
	[super viewWillAppear];
	[[self navigationItem] setTitle:[editingSong album]];
	[tableView reloadData];
}
- (void)setEditingSong:(Song *)song;
{
	editingSong = song;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 4;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
    }
	
	switch(indexPath.section)
	{
		case 0:
			[[cell textLabel] setText: [editingSong album]];
			break;
		case 1:
			[[cell textLabel] setText: [editingSong artist]];
			break;
		case 2:
			[[cell textLabel] setText: [editingSong genre]];
			break;
		case 3:
			[[cell textLabel] setText: [editingSong prix]];
			break;
	}
	return cell;
}
- (NSString *)tableView:(UITableView *)tblView titleForHeaderInSection:(NSInteger)section {
	NSString *sectionName = nil;
	switch(section)
	{
		case 0:
			sectionName = [NSString stringWithString:@"Album"];
			break;
		case 1:
			sectionName = [NSString stringWithString:@"Artiste"];
			break;
		case 2:
			sectionName = [NSString stringWithString:@"Genre"];
			break;
		case 3:
			sectionName = [NSString stringWithString:@"Prix"];
			break;
	}
	return sectionName;
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
    [super viewDidUnload];
	[tableView release];
	tableView=nil;
	[editingSong release];
	editingSong=nil;
}
- (void)dealloc {
	[tableView release];
    [super dealloc];
}
@end

TopSongsAppDelegate.h

#import <UIKit/UIKit.h>
@interface TopSongsAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@end

TopSongsAppDelegate.m

#import "TopSongsAppDelegate.h"
#import "RSSTableViewController.h"
@implementation TopSongsAppDelegate
@synthesize window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    RSSTableViewController *tvc =[[RSSTableViewController alloc] initWithStyle:UITableViewStylePlain];
    UINavigationController *navController =[[UINavigationController alloc] initWithRootViewController:tvc];
    [window addSubview:[navController view]];
    [window makeKeyAndVisible];
    return YES;
}
- (void)dealloc {
    [window release];
    [super dealloc];
}
@end

#2

Hi,

Just finished the challenge myself so thought I’d compare notes - it took me a while to find the attributeDict for the category info !

My version is almost identical to yours apart from the didStartElement and didEndElement methods.

I got rid of the waitingForEntryTitle flag and use the “entry” elementName as the trigger for a new song.

So, in didStartElement I instantiate an empty Song when I get the “entry” elementName and reuse the string in foundCharacters for all the attributes I’m interested in.

In didEndElement I have an if statement to populate the correct song attributes

if ([elementName isEqual:@“im:artist”]) {
currentSong.artist = currentString;
else if …

other than that mine’s virtually the same.

I don’t think there is going to be anything in it regarding optimisation but by reusing the foundCharacters string I’ve got a few less [[alloc ]init]'s going on and consequent release’s so I’d guess it’s marginally faster.

Gareth


#3

Hi,

Just posting how I solved it. I actually have a quite similar solution for the problem where I created an ITMSSong class and a DetailViewController which can display the detail information for a song. This view is popped upon a Navigation Controller when a row is selected, and the current Song for the selected row is passed to it. But I do have slightly different approach when Parsing. I didn’t really use individual NSString instance variables for each element :

RSSTableViewController.m - declaration

@interface RSSTableViewController : UITableViewController <NSXMLParserDelegate>
{
	NSMutableString *parsedString;
	NSMutableArray *songs;
	NSMutableData *xmlData;
	NSURLConnection *connectionInProgress;
	BOOL waitingForAlbumName;
	ITMSSong *song;
}

As you can see, I used a single parsedString which will hold the contents of a single parsed element. This works perfectly assuming that we don’t have nested elements (eg each element contents ends before the next one starts).

Since I noticed we had the im:name element twice (song name and album / collection name), I decided to keep a boolean for that purpose so I could get the contents of both of these name entities.

All the rest is pretty much the same, except that my parsing code looks a bit more simplified due to using a single parsedString :

RSSTableViewController - XML Parsing Code

- (void)parser:(NSXMLParser *)parser 
didStartElement:(NSString *)elementName 
  namespaceURI:(NSString *)namespaceURI 
 qualifiedName:(NSString *)qName 
	attributes:(NSDictionary *)attributeDict
{
	if ([elementName isEqual:@"entry"]) {
		// We should create an ITMSSong and maybe add it to our array
		song = [[ITMSSong alloc] init];
		[songs addObject:song];
	}
	if ([elementName isEqual:@"im:collection"]) {
		NSLog(@"Collection Started");
		waitingForAlbumName = YES;
	}
	if ([elementName isEqual:@"category"] && song ) {
		[song setGenre:[attributeDict objectForKey:@"term"]];
	}
	if (([elementName isEqual:@"title"] ||
		 [elementName isEqual:@"im:image"] ||
		 [elementName isEqual:@"im:artist"] ||
		 [elementName isEqual:@"im:name"] ||
		 [elementName isEqual:@"im:price"]) && song ) {
		parsedString = [[NSMutableString alloc] init];
	}
}

- (void)parser:(NSXMLParser *)parser
foundCharacters:(NSString *)string
{
	[parsedString appendString:string];
}

- (void)parser:(NSXMLParser *)parser 
 didEndElement:(NSString *)elementName 
  namespaceURI:(NSString *)namespaceURI 
 qualifiedName:(NSString *)qName 
{
	if ([elementName isEqual:@"title"] && song) {
		// Put the parsed string into our Song Title
		[song setTitle:parsedString];
		
		[parsedString release];
		parsedString = nil;
	}
	if ([elementName isEqual:@"im:collection"]) {
		NSLog(@"Collection Ended");
		waitingForAlbumName = NO;
	}
	if ([elementName isEqual:@"im:name"] && song) {
		if (waitingForAlbumName) {
			NSLog(@"Album Name");
			[song setAlbum:parsedString];
		}
		else {
			// Put the parsed string into our Song Title
			NSLog(@"Entry Name");
			[song setName:parsedString];
		}
		
		[parsedString release];
		parsedString = nil;
	}
	if ([elementName isEqual:@"im:artist"] && song) {
		// Put the parsed string into our Song Title
		[song setArtist:parsedString];
		
		[parsedString release];
		parsedString = nil;
	}
	if ([elementName isEqual:@"im:price"] && song) {
		// Put the parsed string into our Song Title
		[song setPrice:parsedString];
		
		[parsedString release];
		parsedString = nil;
	}
	if ([elementName isEqual:@"im:image"] && song) {
		// Put the parsed string into our Song Title
		[song setImageURL:parsedString];
		
		[parsedString release];
		parsedString = nil;
	}
	if ([elementName isEqual:@"entry"]) {
		NSLog(@"ended a song entry");
		NSLog(@"song = %@", song );
		[song release];
		song = nil;
	}
}

I think there are still a few ways to improve the code performance though. For example, I should probably be using if … else if … else if … structures or something similar. If the current element is equal to “title” there is no reason to do the other checks at all, since we know it’s title it ceretainly won’t be entry or price or anything else. So for performance reasons I should probably look at getting a better structure than the 7 IF statements I currently have.

As the previous poster mentioned too, I still have a few doubts on the Memory Management. I think I should probably release the NSMutableArray in the dealloc of the RSSTableViewController just to be sure.

Any remarks, suggestions on my approach are more than welcome.

Regards,

Stefaan


#4

Hi,

I tried the example of Stan92 in the first post. Everything works. (Also with my own XML). But there is one problem. The SongDetailView shows the information like Album, Artist name, Genre and Price only the first time correct. When I go back to the Top Song table and I choose another option, the information in the SongDetailView isn’t changed. Everytime I see the information of the first choice.

The code is from the example of Stan92. I made a second xib file (SongDetailViewController.xib) with one Table view.

Can someone help me figure this out?

Regards,
Tim


#5

Hi,

Assuming that your SongDetailViewController.xib is wired up correctly (File Owner --> tableview, TableView Datasource & Delegate --> File Owner)
you should be getting a different detail view for each song.

The line that sets this is [detailSongViewController setEditingSong:[songs objectAtIndex:[indexPath row]]]; in RSSTableViewController
if you had [indexPath section] instead of [indexPath row] you would get the behaviour you’re describing.

Another possibility is the [tableView reloadData]; is missing from ViewWillAppear in SongDetailViewController.m - this would cause the same problem.

HTH
Gareth


#6

Thanx GarethR. It works. The wired up from SongDetailViewController.xib wasn’t correctly.

Is it also possible to show the information of the first table in a Table View object in stead of the hole Window? So it’s possible to use more objects (like buttons) in the first Window. How should you do this?

Regards,
Tim


#7

Hi Tim

Glad it worked.

Stopping a table view that’s managed by a tableviewcontroller from taking up the whole window seems nigh on impossible to me. I’m was trying this for my own app and gave up. In the end I used a UIViewController which seems to be working out fine.

If Joe steps in and gives any other advice then take that over my suggestion as there could be a much simpler way that I’m not aware of.

But in the meantime…

Change RSSTableViewController to a subclass of UIViewController instead and indicate it supports the protocols. Add a UITableView IBOutlet and your button outlets

Create a Window based XIB and add a view, the buttons you want and then a table view resized to occupy the area you want.
Change the File Owner class to your new View based RSS…class.
Wire up the view, tableview and buttons on File Owner. Wire up the table view datasource and delegates.

Your new RSS controller wont support the initWithStyle anymore so change it to a standard init and change the [super initWithStyle] to [super init]
You’ll also need to change the app delegate to alloc init instead of alloc initWithStyle.

I think that’s it.

Good luck. Let me know if it works !

Gareth


#8

Hi Gareth,

That’s a challenge… for me. I have tried to do that, but I need more information to finish this.

In RSSTableViewController.h I changed RSSTableViewController into: RSSTableViewController : UIViewController
In RSSTableViewController.m there are some problems now. I fixed some things, but there are still 3 problems.
[[self tableView] reloadData]; -> ‘RSSTableViewController’ may not respond to '-tableView’
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:@“UITableViewCell”]; -> local declaration of ‘tableView’ hides instance variable
[[self tableView] reloadData]; -> ‘RSSTableViewController’ may not respond to ‘-tableView’

I made a xib file and called it RSSTableview.xib.
Change the File Owner class to your new View based RSS…class.
How should I do this?

I hope you will give me more information to make this work.

Regards,
Tim


#9

Hi,

Sorry - I missed a bit…

you also need a @property (retain, nonatomic) UITableView *tableView in the .h file
and @synthesize tableView in the .m file

change the line UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:@“UITableViewCell”];
to UITableViewCell *cell=[[self tableView] dequeue…

To change the file owner to your new RSS based class:

In interface builder select File Owner then in the menu Tools/Identity Inspector type RSSTableViewController into the Class box.

Hopefully that’s all it needs.

fingers crossed…

Gareth


#10

Hi Gareth,

I still got some problems. Are there any code examples of this?

Regards,
Tim


#11

Tim,

OK - I think you’re nearly there though.

You’ll have to ignore some of this as I’ve been hacking around with a few things but it should give you an idea.

I’ve removed the xib’s and created the tableviews and buttons in the code - I figured that if you’re having problems wiring up the connections in Interface Builder then this should be a bit easier to get running.

Let me know how you get on.

Gareth

Edited - replaced RSSTableViewControler.h and .m with a slightly more streamlined version.

//Song.h #import <Foundation/Foundation.h> @interface Song : NSObject { } @property (nonatomic,copy) NSString *album; @property (nonatomic,copy) NSString *artist; @property (nonatomic,copy) NSString *genre; @property (nonatomic,copy) NSString *price; @end

[code]//Song.m
#import “Song.h”
@implementation Song
@synthesize album, artist,genre, price;
-(void) dealloc
{
[album release];
[artist release];
[genre release];
[price release];
[super dealloc];
}

-(NSString *) description
{
return [NSString stringWithFormat:@“Album:%@\nArtist:%@\nGenre:%@\nPrice:%@\n”,album, artist, genre, price];
}

@end[/code]

[code]//RSSTableViewController.h

#import <UIKit/UIKit.h>
@class Song;
@class SongDetailViewController;
@interface RSSTableViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, NSXMLParserDelegate>
{
NSMutableData *xmlData;
NSURLConnection *connectionInProgress;
SongDetailViewController *songDetailViewController;
}

@property (nonatomic, retain) NSMutableArray *songs;
@property (nonatomic, retain) NSMutableString *currentString;
@property (nonatomic, retain) Song *currentSong;
@property (nonatomic, retain) UITableView *tableView;

-(void)loadSongs;

@end
[/code]

//RSSTableViewController.m

#import <QuartzCore/QuartzCore.h>

#import "RSSTableViewController.h"
#import "Song.h"
#import "SongDetailViewController.h"

#define kStdButtonWidth		106.0
#define kStdButtonHeight	40.0

@implementation RSSTableViewController
@synthesize songs, tableView,currentString, currentSong;

#pragma mark initialisation

- (id)init;
{
	if (!(self = [super init]))
		return nil;
	
	songs=[[NSMutableArray alloc] init];
	
	return self;
}

-(void) loadView
{	
	[self setView: [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]]];
	[[self view] setBackgroundColor:[UIColor groupTableViewBackgroundColor]];
	
	[[self navigationItem] setTitle:@"Top Songs"];
		
	UIButton *okButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
	okButton.frame = CGRectMake(10, 350, kStdButtonWidth, kStdButtonHeight);
	[okButton setTitle:@"Reload" forState:UIControlStateNormal];
	[okButton addTarget:self action:@selector(reload:) forControlEvents:UIControlEventTouchUpInside];
	
	[[self view] addSubview];
	
	UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
	[cancelButton setFrame:CGRectMake(203, 350, kStdButtonWidth, kStdButtonHeight)];
	[cancelButton setTitle:@"Cancel" forState:UIControlStateNormal];
	[cancelButton addTarget:self action:@selector(cancelPressed:) forControlEvents:UIControlEventTouchUpInside];
		
	[[self view] addSubview:cancelButton];
		
	[self setTableView:[[UITableView alloc] initWithFrame:CGRectMake(10, 10, 300, 315) style:UITableViewStylePlain]];
	
	[[self tableView] setDelegate:self];
	[[self tableView] setDataSource:self];
	[[[self tableView] layer] setMasksToBounds:YES];
	[[[self tableView] layer] setCornerRadius:10.0f];
	[[self view] addSubview:tableView];
	[tableView release];
	[self loadSongs];
}
-(void)viewWillAppear:(BOOL)animated
{
	[super viewWillAppear];
	[[self tableView] flashScrollIndicators];
}

-(void)loadSongs
{
	[songs removeAllObjects];
	[[self tableView] reloadData];
	NSURL *url=[NSURL URLWithString:@"http://ax.itunes.apple.com/"
				@"WebObjects/MZStoreServices.woa/ws/RSS/topsongs/"
				@"limit=10/xml"];
	NSURLRequest *request=[NSURLRequest requestWithURL:url
										   cachePolicy:NSURLRequestReloadIgnoringCacheData
									   timeoutInterval:30];
	if (connectionInProgress)
	{
		[connectionInProgress cancel];
		[connectionInProgress release];
	}
	[xmlData release];
	xmlData =[[NSMutableData alloc] init];
	connectionInProgress=[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}

#pragma mark tableView

-(void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
	[tv deselectRowAtIndexPath:indexPath animated:YES];
	
	if (!songDetailViewController) {
		songDetailViewController=[[SongDetailViewController alloc] init];
	}
	[songDetailViewController setEditingSong:[songs objectAtIndex:[indexPath row]]];
	
	[UIView transitionWithView:[[self navigationController] view]
					  duration:1.00
					   options:UIViewAnimationOptionTransitionFlipFromRight
					animations:^{ [[self navigationController] pushViewController:songDetailViewController animated:NO]; }
					completion:NULL];
	
}


-(NSInteger)tableView:(UITableView *)tv numberOfRowsInSection:(NSInteger) section
{
	return [songs count];
	
}
-(UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
	static NSString *CellIdentifier = @"Cell";
	UITableViewCell *cell=[tv dequeueReusableCellWithIdentifier:CellIdentifier];
	if (cell==nil)
	{
		cell=[[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
		[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
	}
	Song *s =[songs objectAtIndex:[indexPath row]];
	[[cell textLabel] setText:[s album]];
	return cell;
}

#pragma mark WebServices Delegates

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
	[xmlData appendData:data];
}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
	NSXMLParser *parser=[[NSXMLParser alloc] initWithData:xmlData];
	[parser setDelegate:self];
	currentString = [NSMutableString string];
	[parser parse];
	[parser release];
	[[self tableView] reloadData];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
	[connectionInProgress release];
	connectionInProgress=nil;
	[xmlData release];
	xmlData=nil;
	NSString *errorString=[NSString stringWithFormat:@"Fetch failed: %@",[error localizedDescription]];
	UIActionSheet *actionSheet=[[UIActionSheet alloc] initWithTitle:errorString delegate:nil 
												  cancelButtonTitle:@"OK"
											 destructiveButtonTitle:nil 
												  otherButtonTitles:nil];
	[actionSheet showInView:[[self view] window]];
	[actionSheet autorelease];
}

#pragma mark XMLParse Delegates
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 
   attributes:(NSDictionary *)attributeDict
{
	if ([elementName isEqual:@"entry"]) {
		currentSong = [[Song alloc] init];
	}
	
	if ([elementName isEqual:@"title"] ||
		[elementName isEqual:@"category"] ||
		[elementName isEqual:@"im:artist"] ||
		[elementName isEqual:@"im:price"]) {
        [currentString setString:@""];
    }
	if ([elementName isEqual:@"category"])
		[currentString appendString:[attributeDict valueForKey:@"term"]];
}

-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 
{
	if ([elementName isEqual:@"entry"]) {
		[songs addObject:currentSong];
		[currentSong release];
        currentSong = nil;
    } else if ([elementName isEqual:@"title"]) {
        currentSong.album = currentString;
    } else if ([elementName isEqual:@"category"]) {
        currentSong.genre = currentString;
    } else if ([elementName isEqual:@"im:artist"]) {
        currentSong.artist = currentString;
    } else if ([elementName isEqual:@"im:price"]) {
        currentSong.price = currentString;
	}
}

-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
		[currentString appendString:string];
}

#pragma mark buttons

-(void) reload:(id)sender
{
	[self loadSongs];
}

-(void) cancelPressed:(id)sender
{
	
	UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Cancel"
														message:@"You pressed Cancel"
													   delegate:self
											  cancelButtonTitle:@"OK"
											  otherButtonTitles:nil];
	[alertView show];
	if (alertView)
		[alertView release];	
}


#pragma mark cleanup

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];    
}
- (void)viewDidUnload {
    [super viewDidUnload];
}
- (void)dealloc {
	[songDetailViewController release];
	[songs release];
	[xmlData release];
	[connectionInProgress release];
    [super dealloc];
}

@end

//TopSongsAppDelegate.h #import <UIKit/UIKit.h> @interface TopSongsAppDelegate : NSObject <UIApplicationDelegate> { UIWindow *window; } @property (nonatomic, retain) IBOutlet UIWindow *window; @end

[code]//TopSongsAppDelegate.m
#import “TopSongsAppDelegate.h”
#import “RSSTableViewController.h”
@implementation TopSongsAppDelegate
@synthesize window;

  • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    RSSTableViewController *tvc =[[RSSTableViewController alloc] init];
    UINavigationController *navController =[[UINavigationController alloc] initWithRootViewController:tvc];
    [window addSubview:[navController view]];
    [window makeKeyAndVisible];
    return YES;
    }
  • (void)dealloc {
    [window release];
    [super dealloc];
    }
    @end[/code]

[code]//SongDetailViewController.h
#import <UIKit/UIKit.h>
@class Song;
@interface SongDetailViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
{
Song *editingSong;
}

@property (retain, nonatomic) UITableView *tableView;

  • (void)setEditingSong:(Song *)song;
    @end[/code]

[code]//SongDetailViewController.m
#import “SongDetailViewController.h”
#import “Song.h”;
@implementation SongDetailViewController

@synthesize tableView;

#pragma mark initialisation

-(void) loadView
{
[self setView:[[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]]];
[[self view] setBackgroundColor:[UIColor groupTableViewBackgroundColor]];

[self setTableView:[[UITableView alloc] initWithFrame:CGRectMake(10, 10, 300, 400) style:UITableViewStyleGrouped]];

[[self tableView] setDelegate:self];
[[self tableView] setDataSource:self];
[[self tableView] setAllowsSelection:NO];
[[self view] addSubview:[self tableView]];
[tableView release];

UIBarButtonItem *backButton = [[[UIBarButtonItem alloc] initWithTitle:@"Songs" style:UIBarButtonItemStylePlain target:self action:@selector(back)] autorelease];
[[self navigationItem] setLeftBarButtonItem:backButton animated:NO];

}

-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear];
[[self navigationItem] setTitle:[editingSong album]];

[tableView reloadData];

}

  • (void)setEditingSong:(Song *)song;
    {
    editingSong = song;
    }

#pragma mark tableView

  • (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 4;
    }

  • (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 1;
    }

  • (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @“Cell”;
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
    cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
    [[cell textLabel] setMinimumFontSize:10.0f];
    [cell textLabel].adjustsFontSizeToFitWidth = YES;
    }

    switch(indexPath.section)
    {
    case 0:
    [[cell textLabel] setText: [editingSong album]];
    break;
    case 1:
    [[cell textLabel] setText: [editingSong artist]];
    break;
    case 2:
    [[cell textLabel] setText: [editingSong genre]];
    break;
    case 3:
    [[cell textLabel] setText: [editingSong price]];
    break;
    }
    return cell;
    }

  • (NSString *)tableView:(UITableView *)tblView titleForHeaderInSection:(NSInteger)section {
    NSString *sectionName = nil;
    switch(section)
    {
    case 0:
    sectionName = [NSString stringWithString:@“Album”];
    break;
    case 1:
    sectionName = [NSString stringWithString:@“Artist”];
    break;
    case 2:
    sectionName = [NSString stringWithString:@“Genre”];
    break;
    case 3:
    sectionName = [NSString stringWithString:@“Price”];
    break;
    }
    return sectionName;
    }

#pragma mark buttons

-(void) back
{
[UIView transitionWithView:[[self navigationController] view]
duration:1.00
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ [[self navigationController] popViewControllerAnimated:NO]; }
completion:NULL];
}

#pragma mark cleanup

  • (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    }
  • (void)viewDidUnload {
    [super viewDidUnload];
    }
  • (void)dealloc {
    [editingSong release];
    [super dealloc];
    }
    @end[/code]

#12

@TimTime (& GarethR)

I am having the exact same issue where only the first item is being fed to the detailviewcontroller properly. If I drill out of the detail view and drill back in to a different item, then the first item’s properties appear again. it is like the editingSong does not update itself in the detail tableview.

I made a xib and gave it a tableview. the tableview’s delegate and dataSource have both been assigned to the File’s Owner, and the File’s Owner tableView has been assigned to the tableview that I added. I changed the File’s Owner class to be my detailviewcontroller. I am still seeing the same behavior. Is there anything else you did? Or maybe I’m missing something simple here.

Thanks all,
Paul


#13

Hi,

I’d sprinkle a few NSLogs in your code to see where it’s going wrong.

Is setEditingSong getting called with the right song?

Is your detail view performing a reloadData in the viewWillAppear method?

If no joy then post some code and I’ll have a look.

Gareth


#14

Ok, I have done all the basic stuff you mentioned, so I will post my file here. One quick note, though. I have modified this to use my own xml file, not apple’s rss feed. I am trying to write an app that will let us log mileage made in company vehicles.

my version of setEditingSong is called setEditingTrip. and all references to “song(s)” are “trip(s)” in my file. it’s fairly 1-to-1 mapping, so it’s not that different (a song’s album is equivalent to my trip’s vehicle, a song’s artist is like my trip’s driver, etc).

From my NSLogs, the editingTrip variable is definitely getting set. the tableview cells are just not repopulating, even though I am calling reloadData

TripDetailViewController.m

//
//  TripDetailViewController.m
//  GTRI_Vehicle
//
//  Created by Paul Brown on 3/28/11.
//  Copyright 2011 Georgia Institute Of Technology. All rights reserved.
//

#import "TripDetailViewController.h"
#import "Trip.h"


@implementation TripDetailViewController

-(void)viewWillAppear:(BOOL)animated
{
	[super viewWillAppear];
	[[self navigationItem] setTitle:[editingTrip tripDriver]];
	NSLog(@"About to reload data. editingtrip tripTimestamp = %@", [editingTrip tripTimestamp]);
	[tableView reloadData];
	NSLog(@"Reloaded data");
}

-(void)viewWillDisappear:(BOOL)animated
{
	NSLog(@"view is disappearing!!");
	//[editingTrip release];
	editingTrip = nil;
}

-(void)setEditingTrip:(Trip *)trip
{
	NSLog(@"Setting editingTrip to: %@",[trip tripTimestamp]);
	editingTrip = trip;
	NSLog(@"Set editingTrip. It is now: %@", [editingTrip tripTimestamp]);
}

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
	return 5;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
	return 1;
}
-(UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	static NSString *CellIdentifier = @"Cell";
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
	if (cell == nil) {
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier] autorelease];
	}
	
	switch(indexPath.section)
	{
		case 0:
			[[cell textLabel] setText:@"Vehicle"];
			[[cell detailTextLabel] setText:[editingTrip tripVehicle]];
			break;
		case 1:
			[[cell textLabel] setText:@"Timestamp"];
			[[cell detailTextLabel] setText:[editingTrip tripTimestamp]];
			break;
		case 2:
			[[cell textLabel] setText:@"Destination: "];
			[[cell detailTextLabel] setText:[editingTrip tripDestination]];
			break;
		case 3:
			[[cell textLabel] setText:@"Mileage: "];
			[[cell detailTextLabel] setText:[editingTrip tripMileage]];
			break;
		case 4:
			[[cell textLabel] setText:@"Driver: "];
			[[cell detailTextLabel] setText:[editingTrip tripDriver]];
			break;
	}
	return cell;
}

-(NSString *)tableView:(UITableView *)tblView titleForHeaderSection:(NSInteger)section {
	NSString *sectionName = nil;
	switch(section)
	{
		case 0:
			sectionName = [NSString stringWithString:@"Vehicle"];
			break;
		case 1:
			sectionName = [NSString stringWithString:@"Timestamp"];
			break;
		case 2:
			sectionName = [NSString stringWithString:@"Destination"];
			break;
		case 3:
			sectionName = [NSString stringWithString:@"Mileage"];
			break;
		case 4:
			sectionName = [NSString stringWithString:@"Driver"];
			break;
	}
	return sectionName;
}

-(void)didReceiveMemoryWarning {
	[super didReceiveMemoryWarning];
}

-(void)viewDidUnload {
	NSLog(@"View unloading!");
	[super viewDidUnload];
	[tableView release];
	tableView = nil;
	[editingTrip release];
	editingTrip = nil;
}

-(void)dealloc {
	[tableView release];
	[super dealloc];
}
			

@end

and just for good measure, since the problem might exist in the parent view controller, here it is:

XMLTableViewController.m (this is like everyone else’s RSSTableViewController.m)

//
//  RXMLTableViewController.m
//  GTRI_Vehicle
//
//  Created by Paul Brown on 3/24/11.
//  Copyright 2011 Georgia Institute Of Technology. All rights reserved.
//

#import "XMLTableViewController.h"
#import "Trip.h"
#import "TripDetailViewController.h"


@implementation XMLTableViewController

@synthesize currentValue;
@synthesize itemElementInProgress;
@synthesize trip;
@synthesize trips;


-(id)init
{
	[super initWithNibName:nil bundle:nil];
	
	UITabBarItem *tbi = [self tabBarItem];
	[tbi setTitle:@"View Log"];
	[tbi setImage:[UIImage imageNamed:@"179-notepad.png"]];
	return self;
}

-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
	return [self init];
}

-(id)initWithStyle:(UITableViewStyle)style
{
	if (self = [super initWithStyle:style]) {
		trips = [[NSMutableArray alloc] init];
	}
	[[self navigationItem] setTitle:@"Most Recent Trips"];
	return self;
}

-(void)viewWillAppear:(BOOL)animated
{
	[super viewWillAppear];
	[self loadTrips];
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
	if (!detailTripViewController) {
		detailTripViewController = [[TripDetailViewController alloc] init];
	}
	
	[detailTripViewController setEditingTrip:[trips objectAtIndex:[indexPath row]]];
	
	[[self navigationController] pushViewController:detailTripViewController animated:YES];
}


-(void)loadTrips
{
	[trips removeAllObjects];
	[[self tableView] reloadData];
	
	NSURL *url = [NSURL URLWithString:@"http://paulbrown.us/vehicle_log/get.php?user=123"];
	NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
	
	if (connectionInProgress) {
		[connectionInProgress cancel];
		[connectionInProgress release];
	}
	
	[xmlData release];
	xmlData = [[NSMutableData alloc] init];
	
	connectionInProgress = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}



- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}


- (void)dealloc {
	[currentValue release];
	[trip release];
    [super dealloc];
}


#pragma mark DataSource

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
	NSLog(@"# of trips: %d",[trips count]);
	return [trips count];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];
	if (cell == nil) {
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"UITableViewCell"] autorelease];
	}
	Trip *t = [trips objectAtIndex:[indexPath row]];
	[cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];
	NSString *driverTimestamp = [[[t tripDriver] stringByAppendingString:@" - "] stringByAppendingString:[[t tripTimestamp] substringToIndex:10]];
	[[cell textLabel] setText:driverTimestamp];
	return cell;
}


#pragma mark WebServices Delegates

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
	[xmlData appendData:data];
}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
	NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
	
	[parser setDelegate:self];
	
	[parser parse];
	
	[parser release];
	[[self tableView] reloadData];
}

-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
	[connectionInProgress release];
	connectionInProgress = nil;
	
	[xmlData release];
	xmlData = nil;
	
	NSString *errorString = [NSString stringWithFormat:@"Fetch failed: %@",[error localizedDescription]];
	UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:errorString
															 delegate:nil
													cancelButtonTitle:@"OK"
											   destructiveButtonTitle:nil
													otherButtonTitles:nil];
	[actionSheet showInView:[[self view] window]];
	[actionSheet autorelease];
}


#pragma mark XMLParse Delegates

-(void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
 namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
   attributes:(NSDictionary *)attributeDict
{
	[currentValue release];
	currentValue = nil;
	
	if ([elementName isEqual:@"trip"]) {
		NSLog(@"found trip!");
		itemElementInProgress = YES;
	}
	if ([elementName isEqual:@"Vehicle"] && itemElementInProgress)
	{
		currentValue = [[NSMutableString alloc] init];
		vehicleString = [[NSMutableString alloc] init];
	}
	if ([elementName isEqual:@"Timestamp"] && itemElementInProgress)
	{
		currentValue = [[NSMutableString alloc] init];
		timestampString = [[NSMutableString alloc] init];
	}
	if ([elementName isEqual:@"Destination"] && itemElementInProgress)
	{
		currentValue = [[NSMutableString alloc] init];
		destinationString = [[NSMutableString alloc] init];
	}
	if ([elementName isEqual:@"Mileage"] && itemElementInProgress)
	{
		currentValue = [[NSMutableString alloc] init];
		mileageString = [[NSMutableString alloc] init];
	}
	if ([elementName isEqual:@"Driver"] && itemElementInProgress)
	{
		currentValue = [[NSMutableString alloc] init];
		driverString = [[NSMutableString alloc] init];
	}
}


-(void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
 namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
	if ([elementName isEqual:@"Vehicle"] && itemElementInProgress)
	{
		[vehicleString setString:currentValue];
		[currentValue release];
		currentValue = nil;
	}
	if ([elementName isEqual:@"Timestamp"] && itemElementInProgress)
	{
		[timestampString setString:currentValue];
		[currentValue release];
		currentValue = nil;
	}
	if ([elementName isEqual:@"Destination"] && itemElementInProgress)
	{
		[destinationString setString:currentValue];
		[currentValue release];
		currentValue = nil;
	}
	if ([elementName isEqual:@"Mileage"] && itemElementInProgress)
	{
		[mileageString setString:currentValue];
		[currentValue release];
		currentValue = nil;
	}
	if ([elementName isEqual:@"Driver"] && itemElementInProgress)
	{
		[driverString setString:currentValue];
		[currentValue release];
		currentValue = nil;
	}
	
	if ([elementName isEqual:@"trip"])
	{
		trip = [[Trip alloc] init];
		[trip setTripDriver:driverString];
		[trip setTripVehicle:vehicleString];
		[trip setTripTimestamp:timestampString];
		[trip setTripDestination:destinationString];
		[trip setTripMileage:mileageString];
		[trips addObject:trip];
		[trip release];
		trip = nil;
		[driverString release];
		[vehicleString release];
		[timestampString release];
		[destinationString release];
		[mileageString release];
		itemElementInProgress = NO;
		[currentValue release];
		currentValue = nil;
	}
}

-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
	//NSLog(@"Found characters: %@", string);
	if (!currentValue) {
		currentValue = [[NSMutableString alloc] init];
	}
	
	[currentValue appendString:string];
}

@end

#15

Hi,

I think I’ve got it

Try this:

add @property (nonatomic, retain) IBOutlet UITableView *tableView; to your TripDetailViewController.h

add @synthesize tableView; to your TripDetailViewController.m

open the TripDetailViewController.xib file and connect the FileOwner’s tableView outlet to your tableView

Let me know if this works.

Gareth


#16

I made those changes and I am still seeing the same behavior. I’m not really sure why the tableview would not get the new data from my editingTrip variable. It successfully gets the correct data the first time it loads, and from my logs I can clearly see that the editingTrip variable changes to the item that I select, but that data just never passes into the cells.


#17

ok, i have it working now. my @interface line in TripDetailViewController.h was

I changed it to @interface TripDetailViewController : UIViewController and it is working.


#18

Hi,

Yes I’d assumed you had inherited from UIViewController.

If it was previously a UITableViewController then I guess you had declared an additional UITableView *tableView in the .h file. Additional because a UITableViewController already comes with a tableView.

In which case this is a new instance variable that hasn’t been initialised and so the [tableView reloadData] in viewWillAppear was a null operation. The super class will trigger the initial load automatically (with the right tableView) hence the first time around its fine.

You could fix this by referring to the ivar as [self tableView] and remove the additional declaration.

Gareth