Please help w/challenge question on pg. 362


#1

I would really like to see the answer to the first challenge question appearing on pg. 362 (re: detail about selected country), but I must have missed any clues in the book about how this might be done. Could someone PLEASE post a detailed solution to this challenge question? I’d be seriously grateful for any help with this. Thanks.


#2

Hi,

Here’s what I did. I moved the main table view out of the app delegate into a CountryTableViewController and then created a CountryDetailViewController and a Country class.

You’ll need to create the CountryDetailViewController.xib in Interface Builder and wire up the outlets from CountryDetailViewController.h

HTH

Gareth

//
//  NayshunzAppDelegate.h
//  Nayshunz
//

#import <UIKit/UIKit.h>

@interface NayshunzAppDelegate : NSObject <UIApplicationDelegate> { 
    IBOutlet UIWindow *window;
	
	NSString *fullPath;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) UINavigationController *navController;

@end
//
//  NayshunzAppDelegate.m
//  Nayshunz
//

#import "NayshunzAppDelegate.h"
#import "CountryTableViewController.h"

@implementation NayshunzAppDelegate

@synthesize window, navController;

- (id)init 
{ 
    self = [super init];
    if (self)
    {
        // Where do the documents go? 
        NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) objectAtIndex:0]; 
        
        // What would be the name of my database file?
        fullPath = [path stringByAppendingPathComponent:@"countries.db"]; 
        
        [fullPath retain];
        // Get a file manager for file operations 
        NSFileManager *fm = [NSFileManager defaultManager]; 
        
        // Does the file already exist? 
        BOOL exists = [fm fileExistsAtPath]; 
        
        // Does it already exist? 
        if (exists) { 
            NSLog(@"%@ exists -- just opening", fullPath); 
        } else { 
            NSLog(@"%@ does not exist -- copying and opening", fullPath); 
            
            // Where is the starting database in the app wrapper? 
            NSString *pathForStartingDB = [[NSBundle mainBundle] pathForResource:@"countries" 
                                                                          ofType:@"db"]; 
            
            // Copy it to the documents directory 
            BOOL success = [fm copyItemAtPath:pathForStartingDB 
                                       toPath:fullPath 
                                        error:NULL]; 
            if (!success) { 
                NSLog(@"database copy failed"); 
            } 
        }
    }
    return self; 
}



#pragma mark App Delegate Methods

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{    
	CountryTableViewController *tvc = [[[CountryTableViewController alloc]
								   initWithStyle:UITableViewStylePlain] autorelease];
	
	[tvc setFullPath];
		
	navController = [[UINavigationController alloc]
											 initWithRootViewController:tvc];
	[window addSubview:[navController view]];
	
    [window makeKeyAndVisible]; 
    
	return YES;
}

- (void)dealloc { 
    [window release]; 
	[fullPath release];
    [navController release];

    [super dealloc]; 
}

@end
//
//  CountryTableViewController.h
//  Nayshunz
//

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

@class CountryDetailViewController;
@class Country;


@interface CountryTableViewController : UITableViewController <UISearchBarDelegate> {

	sqlite3 *databasecopy; 
	
	UISearchBar *searchBar; 
	NSMutableArray *continents; 
	CountryDetailViewController *countryDetailViewController;
	
	// Database stuff 
    sqlite3 *database; 
    sqlite3_stmt *statement;
	sqlite3_stmt *detailStatement;
}

@property (nonatomic, retain) NSMutableArray *countries;
@property (nonatomic, retain) Country *currentCountry;
@property (nonatomic, copy) NSString *fullPath;


-(void)loadCountries;

@end
//
//  CountryTableViewController.m
//  Nayshunz
//


#import "CountryTableViewController.h"
#import "CountryDetailViewController.h"
#import "Country.h"

@implementation CountryTableViewController

@synthesize countries, currentCountry, fullPath;

#pragma mark Initialise Methods 

-(void)loadCountries {
	[[self tableView] reloadData];	
}

-(void)viewDidLoad {
	// Create the UISearchBar and add it to the UINavigationController
	searchBar = [[UISearchBar alloc]init];
	[searchBar sizeToFit];
	[searchBar setDelegate:self];
	[searchBar setPlaceholder:@"Enter the name of a Country"];
    [[self navigationItem] setTitleView: searchBar];
	[searchBar release];	
}

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

-(id) initWithStyle:(UITableViewStyle)style {
    if (self = [super initWithStyle:style]) {
        if (sqlite3_open([fullPath cStringUsingEncoding:NSUTF8StringEncoding], &database) != SQLITE_OK) { 
            NSLog(@"unable to open database at %@", fullPath); 
        }        
        continents = [[NSMutableArray alloc] init];         
		countries = [[NSMutableArray alloc] init];
		[[self navigationItem] setTitle:@"Nayshunz"];
    }	
	return self;
}

#pragma mark Table View Data Source Methods 

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    NSDictionary *continentDict = [continents objectAtIndex:section]; 
    return [continentDict objectForKey:@"name"];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 
    // Return the number of continents 
    return [continents count]; 
} 

-(void) tableView:(UITableView *) tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    
	if (!countryDetailViewController)
		countryDetailViewController = [[CountryDetailViewController alloc] init];
	
	NSDictionary *continentDict = [continents objectAtIndex:[indexPath section]]; 
    // Get the array of nations for this continent
	NSMutableArray *listOfCountries = [continentDict objectForKey:@"list"]; 
	
	// Which nation is at the required row? 
	NSDictionary *nationDict = [listOfCountries objectAtIndex:[indexPath row]]; 
	
    // What is its code? 
    NSString *nationCode = [nationDict objectForKey:@"code"];
	
	currentCountry = [[Country alloc] init];
	
	if (!detailStatement) {
		char *cQuery = "SELECT Code, Name, Continent, Region, SurfaceArea, IndepYear, Population, LifeExpectancy, GNP, GNPOld, LocalName, GovernmentForm, HeadOfState, Capital, Code2 FROM Country " 
		"WHERE Code = ?"; 
		
		if (sqlite3_prepare_v2(database,cQuery, -1, &detailStatement, NULL) != SQLITE_OK) { 
			NSLog(@"Detail query error: %p", detailStatement); 
		}            
	} 
	
	const char *cWhereCondition = [nationCode cStringUsingEncoding:NSUTF8StringEncoding]; 

	int rc = sqlite3_bind_text(detailStatement, 1, cWhereCondition, -1, SQLITE_TRANSIENT);
	
	if (rc != SQLITE_OK)
		NSLog (@"Problem with bind - Error Code: %i",rc);
		
	while (sqlite3_step(detailStatement) == SQLITE_ROW) { 
		
		const char *cContinentCode = (const char *)sqlite3_column_text(detailStatement, 0); 
		if (cContinentCode) {
			NSString *continentCode = [[[NSString alloc] initWithUTF8String:cContinentCode] autorelease]; 
			[currentCountry setCode: continentCode];
		}
		const char *cContinentName = (const char *)sqlite3_column_text(detailStatement, 1); 
		if (cContinentName) {
			NSString *continentName = [[[NSString alloc] initWithUTF8String:cContinentName] autorelease]; 
			[currentCountry setName: continentName];
		}
		const char *cContinentContinent = (const char *)sqlite3_column_text(detailStatement, 2);
		if (cContinentContinent) {
			NSString *continentContinent = [[[NSString alloc] initWithUTF8String:cContinentContinent] autorelease]; 
			[currentCountry setContinent: continentContinent];
		}
		const char *cContinentRegion = (const char *)sqlite3_column_text(detailStatement, 3); 
		if(cContinentRegion) {
			NSString *continentRegion = [[[NSString alloc] initWithUTF8String:cContinentRegion] autorelease]; 
			[currentCountry setRegion: continentRegion];
		}
		NSDecimalNumber *lcontinentSurfaceArea = [[[NSDecimalNumber alloc] initWithDouble:sqlite3_column_double(detailStatement, 4)] autorelease];
		[currentCountry setSurfaceArea: lcontinentSurfaceArea];

		NSNumber *iindepYear = [[[NSNumber alloc] initWithInt:sqlite3_column_int(detailStatement, 5)] autorelease];
		[currentCountry setIndepYear: iindepYear];

		NSNumber *ipopulation = [[[NSNumber alloc] initWithInt:sqlite3_column_int(detailStatement, 6)] autorelease];
		[currentCountry setPopulation: ipopulation];
		
		NSDecimalNumber *lLifeExpectancy = [[[NSDecimalNumber alloc] initWithDouble:sqlite3_column_double(detailStatement, 7)] autorelease];
		[currentCountry setLifeExpectancy: lLifeExpectancy];

		NSDecimalNumber *lGNP = [[[NSDecimalNumber alloc] initWithDouble:sqlite3_column_double(detailStatement, 8)] autorelease];
		[currentCountry setGnp: lGNP];
		
		NSDecimalNumber *lGNPOld = [[[NSDecimalNumber alloc] initWithDouble:sqlite3_column_double(detailStatement, 9)] autorelease];
        [currentCountry setGnpOld: lGNPOld];
		
		const char *cLocalName = (const char *)sqlite3_column_text(detailStatement, 10);
		if (cLocalName) {
			NSString *localName = [[[NSString alloc] initWithUTF8String:cLocalName] autorelease]; 
			[currentCountry setLocalName: localName];
		}
		const char *cGovernmentForm = (const char *)sqlite3_column_text(detailStatement, 11); 
		if(cGovernmentForm) {
			NSString *governmentForm = [[[NSString alloc] initWithUTF8String:cGovernmentForm] autorelease]; 
			[currentCountry setGovernmentForm: governmentForm];
		}
		const char *cHeadOfState = (const char *)sqlite3_column_text(detailStatement, 12); 
		if (cHeadOfState) {
			NSString *headOfState = [[[NSString alloc] initWithUTF8String:cHeadOfState] autorelease]; 
			[currentCountry setHeadOfState: headOfState];
		}
		const char *cCapital = (const char *)sqlite3_column_text(detailStatement, 13);
		if (cCapital) {
			NSString *capital = [[[NSString alloc] initWithUTF8String] autorelease]; 
			[currentCountry setCapital: capital];
		}
		const char *cContinentCode2 = (const char *)sqlite3_column_text(detailStatement, 14); 
		if(cContinentCode2) {
			NSString *continentCode2 = [[[NSString alloc] initWithUTF8String:cContinentCode2] autorelease]; 
			[currentCountry setCode2: continentCode2];
		}
	} 
	sqlite3_reset(detailStatement);
	
	[countryDetailViewController setSelectedCountry:currentCountry];
	
	[[self navigationController] pushViewController:countryDetailViewController
										   animated:YES];
	[currentCountry release], currentCountry = nil;
	[searchBar resignFirstResponder]; 

}

- (NSInteger)tableView:(UITableView *)table 
 numberOfRowsInSection:(NSInteger)section  { 
    // Get the dictionary for the continent for this section 
    NSDictionary *continentDict = [continents objectAtIndex:section]; 
    
    // Get the array of nations for this continent 
	countries = [continentDict objectForKey:@"list"]; 
  
    // Return the number of nations on this continent 
    return [countries count];	
}

- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)ip {	
	static NSString *kCellID = @"UITableViewCell";

    // Get the dictionary for the continent for this section 
    NSDictionary *continentDict = [continents objectAtIndex:[ip section]]; 
    // Get the array of nations for this continent 
	countries = [continentDict objectForKey:@"list"];
  
    // Which nation is at the required row? 
    NSDictionary *nationDict = [countries objectAtIndex:[ip row]]; 
	
    // What is its name? 
    NSString *nationName = [nationDict objectForKey:@"name"]; 
	
    // Try to reuse an existing cell 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellID]; 
    
    // None available? 
    if (!cell) { 
        // Make a new cell 
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                                      reuseIdentifier:kCellID] autorelease]; 
    } 
    
    // Put the name of the country on the cell 

    [[cell textLabel] setText:nationName]; 
    
    return cell; 
} 

#pragma mark Search Methods 

- (void)searchBar:(UISearchBar *)sb textDidChange:(NSString *)searchText { 
    // Clear the data structures 
    [continents removeAllObjects]; 
	
    if ([searchText length] != 0) {
        if (! statement) { 
			// Open the database file 
			if (sqlite3_open([fullPath cStringUsingEncoding:NSUTF8StringEncoding], &database) != SQLITE_OK) { 
				NSLog(@"unable to open database at %@", fullPath); 
			} 			
            char *cQuery = "SELECT Continent, Name, Code FROM Country " 
			"WHERE Name LIKE ? ORDER BY Continent, Name"; 
			
            int rc = sqlite3_prepare_v2(database,cQuery, -1, &statement, NULL); 
            
            if (rc != SQLITE_OK) { 
                NSLog(@"Prepare query error: %d,%p", rc, statement); 
            }            
        } 
        
        searchText = [searchText stringByAppendingString:@"%"]; 
        const char *cSearchText = [searchText cStringUsingEncoding:NSUTF8StringEncoding]; 
        sqlite3_bind_text(statement, 1, cSearchText, -1, SQLITE_TRANSIENT); 
        NSString *lastContinentName = nil; 
        NSMutableArray *currentNationList; 
        
        while (sqlite3_step(statement) == SQLITE_ROW) { 
            
            const char *cContinentName = (const char *)sqlite3_column_text(statement, 0); 
            NSString *continentName = [[[NSString alloc] initWithUTF8String:cContinentName] autorelease]; 
            
            // Is this a new continent? 
            if (!lastContinentName || ![lastContinentName isEqual:continentName]) { 
                // Create an array for the nations of this new continent 
                currentNationList = [[NSMutableArray alloc] init]; 
                
                // Put the name and the array in a dictionary 
                NSDictionary *continentalDict = [[NSDictionary alloc] initWithObjectsAndKeys: 
												 continentName, @"name", currentNationList, @"list", nil]; 
                
                // Release array retained by the dictionary 
                [currentNationList release]; 
                
                // Add the new continent to the array of continents 
                [continents addObject:continentalDict]; 
                
                // Release the dictionary being retained by the array 
                [continentalDict release]; 
            } 
            
            // Note the continent name so that we know if we need to make a 
            // new continent dictionary next time throught the loop 
            lastContinentName = continentName; 
            
            const char *cCountryName = (const char *)sqlite3_column_text(statement, 1); 
            NSString *countryName = [[[NSString alloc] initWithUTF8String:cCountryName] autorelease];
            const char *cCountryCode = (const char *)sqlite3_column_text(statement, 2); 
            NSString *countryCode = [[[NSString alloc] initWithUTF8String:cCountryCode] autorelease]; 
			
            // Create a dictionary for this nation 
            NSMutableDictionary *countryDict = [[NSMutableDictionary alloc] init]; 
            [countryDict setObject:countryName forKey:@"name"]; 
            [countryDict setObject:countryCode forKey:@"code"]; 
            
            // Put the nation's dictionary in the list for the current continent 
            [currentNationList addObject:countryDict]; 
            
            // Release the dictionary retained by the array 
            [countryDict release]; 
		} 
		sqlite3_reset(statement);        
    } 
    
    // Load the table with the new data 
	[[self tableView] reloadData];
	[sb resignFirstResponder]; 

}

#pragma mark Cleanup Methods 

- (void)dealloc {
	[countryDetailViewController release];
	[countries release];
	sqlite3_close(database); 

    [super dealloc];
}


@end
//
//  CountryDetailViewController.h
//  Nayshunz
//

#import <UIKit/UIKit.h>

@class Country;

@interface CountryDetailViewController : UIViewController {


	IBOutlet UILabel *codeField;
	IBOutlet UILabel *surfaceAreaField;
	IBOutlet UILabel *indepYearField;
	IBOutlet UILabel *populationField;
	IBOutlet UILabel *lifeExpectancyField;
	IBOutlet UILabel *gnpField;
	IBOutlet UILabel *gnpOldField;
	IBOutlet UILabel *localNameField;
	IBOutlet UILabel *governmentFormField;
	IBOutlet UILabel *headOfStateField;
	IBOutlet UILabel *capitalField;
	IBOutlet UILabel *code2Field;

	Country *selectedCountry;	
}

@property (nonatomic, assign) Country *selectedCountry;

@end
//
//  CountryDetailViewController.m
//  Nayshunz
//

#import "CountryDetailViewController.h"
#import "Country.h"

@implementation CountryDetailViewController

@synthesize selectedCountry;

- (id) init {
	self = [super initWithNibName:@"CountryDetailViewController" bundle:nil];
	return self;
}

-(void) viewWillAppear:(BOOL)animated {
	[super viewWillAppear];
	
	[codeField setText:[selectedCountry code]];
	[surfaceAreaField setText:[[selectedCountry surfaceArea] stringValue]];
	[indepYearField setText:[[selectedCountry indepYear]stringValue]];
	[populationField setText:[[selectedCountry population]stringValue]];
	[lifeExpectancyField setText:[[selectedCountry lifeExpectancy]stringValue]];
	[gnpField setText:[[selectedCountry gnp]stringValue]];
	[gnpOldField setText:[[selectedCountry gnpOld]stringValue]];
	[localNameField setText:[selectedCountry localName]];
	[governmentFormField setText:[selectedCountry governmentForm]];
	[headOfStateField setText:[selectedCountry headOfState]];
	[capitalField setText:[selectedCountry capital]];
	[code2Field setText:[selectedCountry code2]];	

	[[self navigationItem] setTitle:[selectedCountry name]];
}

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

- (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];
	[codeField release], codeField = nil;
	[surfaceAreaField release],	surfaceAreaField = nil;
	[indepYearField release], indepYearField = nil;
	[populationField release], populationField = nil;
	[lifeExpectancyField release], lifeExpectancyField = nil;
	[gnpField release],	gnpField = nil;
	[gnpOldField release], gnpOldField = nil;
	[localNameField release], localNameField = nil;
	[governmentFormField release], governmentFormField = nil;
	[headOfStateField release],	headOfStateField = nil;
	[capitalField release],	capitalField = nil;
	[code2Field release], code2Field = nil;
}

- (void)dealloc {
	[codeField release];
	[surfaceAreaField release];
	[indepYearField release];
	[populationField release];
	[lifeExpectancyField release];
	[gnpField release];
	[gnpOldField release];
	[localNameField release];
	[governmentFormField release];
	[headOfStateField release];
	[capitalField release];
	[code2Field release];
	
    [super dealloc];
}

@end
//
//  Country.h
//  Nayshunz
//

#import <Foundation/Foundation.h>

@interface Country : NSObject {}

@property (nonatomic, copy) NSString *code;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *continent;
@property (nonatomic, copy) NSString *region;
@property (nonatomic, copy) NSNumber *surfaceArea;
@property (nonatomic, copy) NSNumber *indepYear;
@property (nonatomic, copy) NSNumber *population;
@property (nonatomic, copy) NSNumber *lifeExpectancy;
@property (nonatomic, copy) NSNumber *gnp;
@property (nonatomic, copy) NSNumber *gnpOld;
@property (nonatomic, copy) NSString *localName;
@property (nonatomic, copy) NSString *governmentForm;
@property (nonatomic, copy) NSString *headOfState;
@property (nonatomic, copy) NSString *capital;
@property (nonatomic, copy) NSString *code2;


@end
//
//  Country.m
//  Nayshunz
//

#import "Country.h"

@implementation Country

@synthesize code,name,continent,region,surfaceArea,indepYear,population,lifeExpectancy,gnp,gnpOld,
localName,governmentForm,headOfState,capital,code2;

-(void) dealloc
{
	[code release];
	[name release];
	[continent release];
	[region release];
	[surfaceArea release];
	[indepYear release];
	[population release];
	[lifeExpectancy release];
	[gnp release];
	[gnpOld release];
	[localName release];
	[governmentForm release];
	[headOfState release];
	[capital release];
	[code2 release];

	[super dealloc];
}

@end

#3

Wow…that’s great. I am anxious to try this but probably won’t be able to get to it until Monday. I am ever so grateful for your help! Thank you so much!