First IOS App Doesn't Store and Display the Tasks Entered


#1

I followed chapter 27 and was able to build the app without error, but when I interact with the IOS app using the simulator it only allows me to enter the to do list task, but it does not save and display the to do list task I entered. Can someone help pinpoint where I went wrong?

I had a question on what this line of code does. Is it suppose to be like my mini database to store my to do list? Is this where I need to fix it to tailor it to my local machine? Where is data.td save to my computer? Where is this database stored?

NSArray *pathList = NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES);
return [[pathList objectAtIndex:0] stringByAppendingPathComponent:@“data.td”];

This is the bnrappdelegate.h code

#import <UIKit/UIKit.h>

//Declare a helper function that we will use to get a path
//to the location on disk where we can save the to do list

NSString *docPath(void);

@interface BNRAppDelegate : UIResponder <UIApplicationDelegate, UITableViewDataSource>
{
    UITableView *taskTable;
    UITextField *taskField;
    UIButton *insertButton;
    
    NSMutableArray *tasks;
}

- (void) addTask: (id) sender;


@property (strong, nonatomic) UIWindow *window;

@end

This is the bnrappdelegate.m code

#import "BNRAppDelegate.h"

//Helper function to fetch the path to our to do data stored on disk
NSString *docPath()
{
    NSArray *pathList = NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES);
    return [[pathList objectAtIndex:0] stringByAppendingPathComponent:@"data.td"];
}

@implementation BNRAppDelegate

#pragma mark - Application delegate callbacks

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
       
    //Attempt to load an existing to do dataset from an array stored to disk.
    NSArray *plist = [NSArray arrayWithContentsOfFile:docPath()];
    if (plist){
        //If there was a dataset available, copy it into our instance variable.
        tasks = [plist mutableCopy];
    }else{
        //Otherwise, just create an empty one to get us started.
        tasks = [[NSMutableArray alloc] init];
    }
    
    //Is tasks empty?
    if ([tasks count] == 0){
        //Put some strings in it
        [tasks addObject:@"Walk"];
        [tasks addObject:@"Study"];
        [tasks addObject:@"Exercise"];
    }
    
    //Create and configure the UIWindow instance
    //A CGRect is a struct with an origin (x,y) and size (width,height)
    CGRect windowFrame = [[UIScreen mainScreen] bounds];
    UIWindow *theWindow = [[UIWindow alloc] initWithFrame:windowFrame];
    [self setWindow:theWindow];
    
    //Define the frame rectangles of the three UI elements
    //CGRectMake() creates a CGRect from (x,y,width,height)
    CGRect tableFrame = CGRectMake(0,80,320,380);
    CGRect fieldFrame = CGRectMake(20,40,200,31);
    CGRect buttonFrame = CGRectMake(228,40,72,31);
    
    //Create and configure the table view
    taskTable = [[UITableView alloc] initWithFrame:tableFrame style:UITableViewStylePlain];
    [taskTable setSeparatorStyle:UITableViewCellSeparatorStyleNone];
    
    //Create and configure the text field where new tasks will be typed
    taskField = [[UITextField alloc] initWithFrame:fieldFrame];
    [taskField setBorderStyle:UITextBorderStyleRoundedRect];
    [taskField setPlaceholder:@"Type a task, tap Insert"];
    
    //Create and configure a rounded rect Insert button
    insertButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [insertButton setFrame:buttonFrame];
    
    //Buttons behave using a target/action callback
    //Configure the Insert button's action to call this object's -addTask; method
    [insertButton addTarget:self action:@selector(addTask:) forControlEvents:UIControlEventTouchUpInside];
    
    //Give the button a title
    [insertButton setTitle:@"Insert" forState:UIControlStateNormal];
    
    //Add our three UI elements to the window
    [[self window] addSubview:taskTable];
    [[self window] addSubview:taskField];
    [[self window] addSubview:insertButton];
    
    //Finalize the window and put it on the screen
    [[self window] setBackgroundColor: [UIColor whiteColor]];
    [[self window] makeKeyAndVisible];

    
    //Create and configure the table view
    taskTable = [[UITableView alloc] initWithFrame:tableFrame style:UITableViewStylePlain];
    [taskTable setSeparatorStyle:UITableViewCellSeparatorStyleNone];
    
    //Make this object the table view's dataSource
    [taskTable setDataSource:self];
    
    //Create and configure the text field where new tasks will be typed
    taskField = [[UITextField alloc] initWithFrame:fieldFrame];
    
      return YES;
     
}

#pragma mark - Table View management

- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInsection: (NSInteger)section
{
    //Because this table view only has one section,
    //the number of rows in it is equal to the number
    //of times in our tasks array
    return [tasks count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    //To improve performance, we reconfigure cells in memory
    //that have scrolled off the screen and hand them back
    //with new contents instead of always creating new cells.
    //First, we check to see if there's a cell available for reuse.
    UITableViewCell *c = [taskTable dequeueReusableCellWithIdentifier:@"Cell"];
    
    if (!c) {
        //... and only allocate a new cell if none are available
        c = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
    }
    
    //Then we (re)configure the cell based on the model object,
    //in this case our todoItems array
    NSString *item = [tasks objectAtIndex:[indexPath row]];
    [[c textLabel] setText:item];
    
    //and hand back to the table view the properly configured cell
    return c;
}

- (void)addTask:(id)sender
{
    //Get the to do item
    NSString *t = [taskField text];
    
    //Quit here if taskField is empty
    if ([t isEqualToString:@""]) {
        return;
    }
    
    //Add it to our working array
    [tasks addObject:t];
    //Refresh the table so that the new item shows up
    [taskTable reloadData];
    //And clear out the text field
    [taskField setText:@""];
    //Dismiss the keyboard
    [taskField resignFirstResponder];
    
}

- (void)applicationWillResignActive:(UIApplication *)application
{
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    
    //This method is only called in iOS 4.0+
    //Save our tasks array to disk
    [tasks writeToFile:docPath() atomically:YES];
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    //This method is only called in iOS versions prior to 4.0
    //Save our tasks array to disk
    [tasks writeToFile:docPath() atomically:YES];
}

@end

#2

Try this:

...
NSLog (@"---> DocPath: %@", docPath());
...

#3

Where do I run this NSLog line of code from? I’m only seeing output with the IOS simulator.
I tried to open a new command line project to try this, but it doesn’t work even if I grabbed the docPath() code.

NSString *docPath()
{
NSArray *pathList = NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES);
return [[pathList objectAtIndex:0] stringByAppendingPathComponent:@“data.td”];
}


#4

You can put it in BNRAppDelegate.m.

BNRAppDelegate.m[code]

  • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {

    NSLog (@"—> DocPath: %@", docPath());

    }
    [/code]

#5

It didn’t work. I removed the NSLog line of coode. I reran the app and I get this error.

main.m 

#import <UIKit/UIKit.h>

#import "BNRAppDelegate.h"

int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([BNRAppDelegate class]));   [b]-->Thread 1: signal SIGABRT[/b]

        
        
    }

}

#6

I’m recoding this exercise to see if I messed up anywhere along the way and I found some typos, but when I ran it again on page 188 to test that the test data gets displayed I don’t see it. I get this error and the line @implementation BNAPPDelegate is highlighted with message Incomplete implementation

2013-01-21 11:14:56.983 iTahDoodle2[1055:c07] Application windows are expected to have a root view controller at the end of application launch

#import "BNRAppDelegate.h"

//Helper function to fetch the path to our to do data stored on disk
NSString *docPath()
{
    NSArray *pathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    return [[pathList objectAtIndex:0] stringByAppendingPathComponent:@"data.td"];
}

@implementation BNRAppDelegate

{}
#pragma mark - Application delegate callbacks

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //Attempt to load an existing to do dataset from an array stored to disk
    NSArray *plist = [NSArray arrayWithContentsOfFile:docPath()];
    if (plist){
        //If there was a dataset available, copy it into our instance variable.
        tasks = [plist mutableCopy];
    } else {
        //Otherwise, just create an empty one to get us started.
        tasks = [[NSMutableArray alloc] init];
    }
    
    //Is tasks empty?
    if ([tasks count] == 0){
        //Put some strings in it
        [tasks addObject:@"Walk"];
        [tasks addObject:@"Study"];
        [tasks addObject:@"Exercise"];
    }
    
    //Create and configure the UIWindow instance
    //A CGRect is a struct with an origin (x,y) and size (width,height)
    CGRect windowFrame = [[UIScreen mainScreen] bounds];
    UIWindow *theWindow = [[UIWindow alloc] initWithFrame:windowFrame];
    [self setWindow:theWindow];
    
    //Define the frame rectangles of the three UI elements
    //CGRectMake() creates a CGRect from (x,y,width,height)
    CGRect tableFrame = CGRectMake(0,80,320,380);
    CGRect fieldFrame = CGRectMake(20,40,200,31);
    CGRect buttonFrame = CGRectMake(228,40,72,31);
    
    //Create and configure the table view
    taskTable = [[UITableView alloc] initWithFrame:tableFrame style:UITableViewStylePlain];
    [taskTable setSeparatorStyle:UITableViewCellSeparatorStyleNone];
    
    //Create and configure the text field where new tasks will be typed
    taskField = [[UITextField alloc] initWithFrame:fieldFrame];
    [taskField setBorderStyle:UITextBorderStyleRoundedRect];
    [taskField setPlaceholder:@"Type a task, tap Insert"];
    
    //Create and configure a rounded rect Insert button
    insertButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [insertButton setFrame:buttonFrame];
    
    //Buttons behave using a target/action callback
    //Configure the Insert button's action to call this object's -addTask; method
    [insertButton addTarget:self action:@selector(addTask:) forControlEvents:UIControlEventTouchUpInside];
    
    //Give the button a title
    [insertButton setTitle:@"Insert" forState:UIControlStateNormal];
    
    //Add our three UI elements to the window
    [[self window] addSubview:taskTable];
    [[self window] addSubview:taskField];
    [[self window] addSubview:insertButton];
    
    //Finalize the window and put it on the screen
    [[self window] setBackgroundColor: [UIColor whiteColor]];
    [[self window] makeKeyAndVisible];
    
    //Create and configure the table view
    taskTable = [[UITableView alloc] initWithFrame:tableFrame style:UITableViewStylePlain];
    [taskTable setSeparatorStyle:UITableViewCellSeparatorStyleNone];
    
    //Make this object the table view's dataSource
    [taskTable setDataSource:self];
    
    //Create and configure the text field where new tasks will be typed
    taskField = [[UITextField alloc] initWithFrame:fieldFrame];


    return YES;
    
}



#pragma mark - Table View management

- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section
{
    //Because this table view only has one section,
    //the number of rows in it is equal to the number
    //of times in our tasks array
    return [tasks count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    //To improve performance, we reconfigure cells in memory
    //that have scrolled off the screen and hand them back
    //with new contents instead of always creating new cells.
    //First, we check to see if there's a cell available for reuse.
    UITableViewCell *c = [taskTable dequeueReusableCellWithIdentifier:@"Cell"];
    
    if (!c) {
        //... and only allocate a new cell if none are available
        c = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
    }
    
    //Then we (re)configure the cell based on the model object,
    //in this case our todoItems array
    NSString *item = [tasks objectAtIndex:[indexPath row]];
    [[c textLabel] setText:item];
    
    //and hand back to the table view the properly configured cell
    return c;
}



- (void)applicationWillResignActive:(UIApplication *)application
{
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

@end
#import <UIKit/UIKit.h>

//Declare a helper function that we will use to get a path
//to the location on disk where we can save the to do list

NSString *docPath(void);

@interface BNRAppDelegate : UIResponder <UIApplicationDelegate, UITableViewDataSource>
{
    UITableView *taskTable;
    UITextField *taskField;
    UIButton *insertButton;
    
    NSMutableArray *tasks;
}

-(void)addTask:(id)sender;

@property (strong, nonatomic) UIWindow *window;

@end

#7

Also when I ran the program on page 189 I get another error

2013-01-21 11:30:38.239 iTahDoodle2[1124:c07] Application windows are expected to have a root view controller at the end of application launch
2013-01-21 11:30:51.819 iTahDoodle2[1124:c07] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil’
*** First throw call stack:
(0x1c8e012 0x10cbe7e 0x1c41b6a 0x1c41a20 0x396e 0x10df705 0x16920 0x168b8 0xd7671 0xd7bcf 0xd6d38 0x4633f 0x46552 0x243aa 0x15cf8 0x1be9df9 0x1be9ad0 0x1c03bf5 0x1c03962 0x1c34bb6 0x1c33f44 0x1c33e1b 0x1be87e3 0x1be8668 0x1365c 0x2a5d 0x2985)
libc++abi.dylib: terminate called throwing an exception
(lldb)

#import "BNRAppDelegate.h"

//Helper function to fetch the path to our to do data stored on disk
NSString *docPath()
{
    NSArray *pathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    return [[pathList objectAtIndex:0] stringByAppendingPathComponent:@"data.td"];
}

@implementation BNRAppDelegate

{}
#pragma mark - Application delegate callbacks

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //Attempt to load an existing to do dataset from an array stored to disk
    NSArray *plist = [NSArray arrayWithContentsOfFile:docPath()];
    if (plist){
        //If there was a dataset available, copy it into our instance variable.
        tasks = [plist mutableCopy];
    } else {
        //Otherwise, just create an empty one to get us started.
        tasks = [[NSMutableArray alloc] init];
    }
    
    //Is tasks empty?
    if ([tasks count] == 0){
        //Put some strings in it
        [tasks addObject:@"Walk"];
        [tasks addObject:@"Study"];
        [tasks addObject:@"Exercise"];
    }
    
    //Create and configure the UIWindow instance
    //A CGRect is a struct with an origin (x,y) and size (width,height)
    CGRect windowFrame = [[UIScreen mainScreen] bounds];
    UIWindow *theWindow = [[UIWindow alloc] initWithFrame:windowFrame];
    [self setWindow:theWindow];
    
    //Define the frame rectangles of the three UI elements
    //CGRectMake() creates a CGRect from (x,y,width,height)
    CGRect tableFrame = CGRectMake(0,80,320,380);
    CGRect fieldFrame = CGRectMake(20,40,200,31);
    CGRect buttonFrame = CGRectMake(228,40,72,31);
    
    //Create and configure the table view
    taskTable = [[UITableView alloc] initWithFrame:tableFrame style:UITableViewStylePlain];
    [taskTable setSeparatorStyle:UITableViewCellSeparatorStyleNone];
    
    //Create and configure the text field where new tasks will be typed
    taskField = [[UITextField alloc] initWithFrame:fieldFrame];
    [taskField setBorderStyle:UITextBorderStyleRoundedRect];
    [taskField setPlaceholder:@"Type a task, tap Insert"];
    
    //Create and configure a rounded rect Insert button
    insertButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [insertButton setFrame:buttonFrame];
    
    //Buttons behave using a target/action callback
    //Configure the Insert button's action to call this object's -addTask; method
    [insertButton addTarget:self action:@selector(addTask:) forControlEvents:UIControlEventTouchUpInside];
    
    //Give the button a title
    [insertButton setTitle:@"Insert" forState:UIControlStateNormal];
    
    //Add our three UI elements to the window
    [[self window] addSubview:taskTable];
    [[self window] addSubview:taskField];
    [[self window] addSubview:insertButton];
    
    //Finalize the window and put it on the screen
    [[self window] setBackgroundColor: [UIColor whiteColor]];
    [[self window] makeKeyAndVisible];
    
    //Create and configure the table view
    taskTable = [[UITableView alloc] initWithFrame:tableFrame style:UITableViewStylePlain];
    [taskTable setSeparatorStyle:UITableViewCellSeparatorStyleNone];
    
    //Make this object the table view's dataSource
    [taskTable setDataSource:self];
    
    //Create and configure the text field where new tasks will be typed
    taskField = [[UITextField alloc] initWithFrame:fieldFrame];


    return YES;
    
}



#pragma mark - Table View management

- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section
{
    //Because this table view only has one section,
    //the number of rows in it is equal to the number
    //of times in our tasks array
    return [tasks count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    //To improve performance, we reconfigure cells in memory
    //that have scrolled off the screen and hand them back
    //with new contents instead of always creating new cells.
    //First, we check to see if there's a cell available for reuse.
    UITableViewCell *c = [taskTable dequeueReusableCellWithIdentifier:@"Cell"];
    
    if (!c) {
        //... and only allocate a new cell if none are available
        c = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
    }
    
    //Then we (re)configure the cell based on the model object,
    //in this case our todoItems array
    NSString *item = [tasks objectAtIndex:[indexPath row]];
    [[c textLabel] setText:item];
    
    //and hand back to the table view the properly configured cell
    return c;
}

- (void) addTask: (id) sender
{
    //Get the to do item
    NSString *t = [taskField text];
    
    //Quit here if taskField is empty
    if ([t isEqualToString:@""]) {
        return;
    }
    
    //Add it to our working array
    [tasks addObject:t];
    //Refresh the table so that the new item shows up
    [taskTable reloadData];
    //And clear out the text field
    [taskField setText:@""];
    //Dismiss the keyboard
    [taskField resignFirstResponder];
    
}

- (void)applicationWillResignActive:(UIApplication *)application
{
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

#8

I’m using iOS 6 Simulator and the book is using iOS 5 Simulator. Could this be the reason why I’m seeing so much problems with my code?


#9

I’m seeing the path of the data.td now. It is stored in the iOS Simulator but I had to retype all my code. But the app still doesn’t save and display the tasks I entered. I keep getting the error -> Applications are expected to have a root view controller at the end of application launch.