PDA

View Full Version : [Resolved] Chapter 8 TableView unrecognized selector




Parradoxx
Apr 19, 2011, 05:12 PM
Greetings,

I'm working my way through "Beginning iOS 4 Application Programming" by Wei-Meng Lee. I'm using XCode 4.0.1, but the book was written for an earlier version. So far, I've had no problem adapting the lesson material to any subtle changes.

That said, Chapter 8 focuses on using tableView. The first exercise populated the view from an array. This exercise introduces plists.

exception 'NSInvalidArgumentException', reason: '-[__NSCFDictionary objectAtIndex:]: unrecognized selector sent to instance 0x8e038f0'

SIGBART occurs on the last line of this code from tableView:cellForRowAtIndexPath:
NSString *year = [self.years objectAtIndex:[indexPath section]];
NSArray *movieSection = [self.movieTitles objectForKey:year];
cell.textLabel.text = [movieSection objectAtIndex:[indexPath row]];

*year has the value of "Root", which explains why *movieSection yields "Variable is not a CFArray". Okay, so I think I see the problem, but I'm not sure what is causing the problem. self.movieTitles and self.years are populated up in viewDidLoad:
- (void)viewDidLoad
{
NSString *localPath = [[NSBundle mainBundle] pathForResource:@"Movies" ofType:@"plist"];
NSDictionary *dic = [[NSDictionary alloc] initWithContentsOfFile:localPath];
self.movieTitles = dic;

[dic release];

NSArray *array = [[self.movieTitles allKeys] sortedArrayUsingSelector:@selector(compare:)];
self.years = array;

[super viewDidLoad];
}

It builds successfully. I've combed it for typos compared to the book, and finding none have concluded that there's some kind of change between XCode versions/frameworks, or I've bunged up the .plist somehow. Obviously, another pair of eyes checking for typos would be helpful.

Below is a copy-n-paste of the plist from XCode to the forum input. If there's a better way of showing a plist, please let me know.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>2008</key>
<array>
<string>The Great Debaters</string>
<string>American Gansters</string>
</array>
<key>2006</key>
<array>
<string>A Hand to Guide Me</string>
<string>Inside Man</string>
</array>
<key>2007</key>
<array>
<string>Deja Vu</string>
</array>
<key>2000</key>
<array>
<string>Malcom X</string>
<string>The Pelican Brief</string>
<string>The Hurricane</string>
</array>
<key>2004</key>
<array>
<string>Man on Fire</string>
<string>Out of TIme</string>
<string>Training Day</string>
<string>License to Kill</string>
<string>Carbon Copy</string>
</array>
<key>2002</key>
<array>
<string>John Q.</string>
</array>
<key>2001</key>
<array>
<string>Remember the Titans</string>
<string>The Bone Collector</string>
</array>
</dict>
</plist>

I'm sure this is something simple, and I appreciate your assistance in setting this newbie back on the road of progress. Thanks!



chown33
Apr 19, 2011, 06:37 PM
Have you tried using the debugger?

If not, then now is a good time to learn it, when programs are relatively simple.

Set a breakpoint at the first line, where you assign a value to the local years. Then step through one line at a time, examining variables to make sure they're really what you expect.

You might also inspect the value of [indexPath section], to make sure it's what you expect.


Have you tried adding code that shows the type and/or contents of what you believe to be an NSArray and an NSDictionary?

The NSLog() function will be useful for this.


Right now, it seems like your only debugging strategy is to look for typos (important, but inadeqate), or to ask others to look for typos or make guesses (also inadequate). You have to learn how to Break It Down, and the way to do that is with the debugger, and testing every step.

One of the problems that others might have is you haven't supplied sufficient code or context for them to replicate the problem. Saying that it's an exact duplicate from a book doesn't count; you could have a typo in the code you haven't posted, where someone else wouldn't make the same mistake.

Parradoxx
Apr 19, 2011, 07:40 PM
Thanks Chown,

You are correct, that right now my strategy is looking for typos...only because the point of the book lesson is to take me through the learning one-phase at a time. Assuming the book is worth it's salt, it is reasonable to expect that all of the exercises should run as published. Thus the conclusions that I have drawn.

However, I do appreciate your encouragement to dive into debugging. It's a later chapter *grin* but there's nothing stopping me from skipping ahead.

I'll jump into that after this posting; in the meantime, here's the full code (at least, of those files that have been touched). Last post, I included almost all of the code which had been customized -- but I suppose it is possible that I bumped the spacebar and munged some of the factory code. As you said, hunting for typos is important, if inadequate. *smile*

TableViewAppDelegate.m
//
// Note: I'm pretty sure TableViewAppDelegate.h is XCode pristine
//

#import "TableViewAppDelegate.h"

@implementation TableViewAppDelegate

@synthesize window=_window;

@synthesize navigationController=_navigationController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Add the navigation controller's view to the window and display.
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application
{
/* comment removed */
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
/* comment removed */
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
/* comment removed */
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
/* comment removed */
}

- (void)applicationWillTerminate:(UIApplication *)application
{
/* comment removed */
}

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

@end

RootViewController.h
#import <UIKit/UIKit.h>

@interface RootViewController : UITableViewController {
NSDictionary *movieTitles;
NSArray *years;
}

@property (nonatomic, retain) NSDictionary *movieTitles;
@property (nonatomic, retain) NSArray *years;

@end

RootViewController.m
#import "RootViewController.h"

@implementation RootViewController

@synthesize movieTitles;
@synthesize years;

- (void)viewDidLoad
{
NSString *localPath = [[NSBundle mainBundle] pathForResource:@"Movies" ofType:@"plist"];
NSDictionary *dic = [[NSDictionary alloc] initWithContentsOfFile:localPath];
self.movieTitles = dic;

[dic release];

NSArray *array = [[self.movieTitles allKeys] sortedArrayUsingSelector:@selector(compare:)];
self.years = array;

[super viewDidLoad];
}

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

- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}

/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations.
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/

// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
//return 1;
return [self.years count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//return 0;

NSString *year = [self.years objectAtIndex:section];
NSArray *movieSection = [self.movieTitles objectForKey:year];
return [movieSection count];
}

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}

// Configure the cell.
NSString *year = [self.years objectAtIndex:[indexPath section]];
NSArray *movieSection = [self.movieTitles objectForKey:year];
cell.textLabel.text = [movieSection objectAtIndex:[indexPath row]];

return cell;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
NSString *year = [self.years objectAtIndex:section];
return year;
}

/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
*/

/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// Delete the row from the data source.
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert)
{
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
*/

/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/

/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
/*
<#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:@"<#Nib name#>" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
*/
}

- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];

// Relinquish ownership any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload
{
[super viewDidUnload];

// Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
// For example: self.myOutlet = nil;
}

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

@end


Thanks for your input!!

PhoneyDeveloper
Apr 19, 2011, 08:13 PM
In case you don't understand that SIGABRT error message let me explain it. The error is a did not understand selector error. That means that your code attempted to call a method on an object and that object didn't implement that method. In this case your code is attempting to call movieSection objectAtIndex:. objectAtIndex is of course a method of NSArray. The error says that movieSection is an NSDictionary. So the proximate cause of that error is that what you believe to be an NSArray is really an NSDictionary.

I looked over the code in your first post and I don't see an error. The most likely error would be that the plist is not in the format you think it is.

Anyway, work on figuring out why the code gets a dictionary when it should get an array.

Parradoxx
Apr 19, 2011, 08:26 PM
There's good news, and good news.

The good news is, it was as I suspected, and Phoney affirmed -- I botched the plist. In the book, on page 212, step 9 reads in part, "Select it [the plist] and create the list of items as shown in Figure 8-15." The screenshot from the older XCode shows the editor with the first item being "Root" of type "Dictionary", something which the Xcode 4.0.1 editor also let me do.

The other good news is Chown's encouragement to set a breakpoint. I decided to monkey before actually "doing" the troubleshooting chapter, and set the break as Chown advised. "Root" was coming up as a value, throwing everything off. So I created a new plist file, copied the content from the old one, which conveniently did NOT paste the Root entry, but only the content arrays.

Renamed, removed the breakpoint, and it built and ran like a charm.

I still don't know WHY it didn't work -- that is, why this would have worked before, or at least why the author thought it would have worked...I'll be writing the publisher. But at least I can continue my learning.

Thanks!

chown33
Apr 19, 2011, 09:17 PM
The other good news is Chown's encouragement to set a breakpoint. I decided to monkey before actually "doing" the troubleshooting chapter, and set the break as Chown advised. "Root" was coming up as a value, throwing everything off. So I created a new plist file, copied the content from the old one, which conveniently did NOT paste the Root entry, but only the content arrays.

Good use of initiative. Glad it worked out.


I still don't know WHY it didn't work -- that is, why this would have worked before, or at least why the author thought it would have worked...I'll be writing the publisher. But at least I can continue my learning.

Most books have a related website, with downloadable source code, data files, and lists of errata in the book.

I would have found the website, grabbed the original data file (plist), and used that as my test case. It could still malfunction, but at least you'd be using the author's data.

PhoneyDeveloper
Apr 19, 2011, 09:37 PM
I think I've seen a number of similar reports where the plist editor works differently than it used to and adds an unexpected object at the root.

OTOH, I always build my plists in code rather than use the plist editor. That way I know exactly what they are.