Saving issues

Hello there!

I have been working through this chapter and my app is built, everything runs fine, I can edit the text (after some work and reworking), but the app won’t save any tasks I put into it. I have gone through Document.m multiple times and my implementation seems fine and I would appreciate another pair of eyes looking through it.

Thank you!

[code]#import “Document.h”

@interface Document ()

@end

@implementation Document

#pragma mark - NSDocument Overrides

  • (instancetype)init {
    self = [super init];
    if (self) {
    // Add your subclass-specific initialization here.
    }
    return self;
    }

  • (void)windowControllerDidLoadNib:(NSWindowController *)aController {
    [super windowControllerDidLoadNib:aController];
    // Add any code here that needs to be executed once the windowController has loaded the document’s window.
    }

  • (BOOL)autosavesInPlace {
    return YES;
    }
  • (NSString *)windowNibName {
    // Override returning the nib file name of the document
    // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
    return @“Document”;
    }

  • (NSData *)dataOfType:(NSString *)typeName
    error:(NSError **)outError
    {
    // This method is called when our document is being saved
    // You are expected to hand the caller an NSData object wrapping our data
    // so that it can be written to disk
    // If there is no array, write out an empty array
    if (!self.tasks) {
    self.tasks = [NSMutableArray array];
    }

    // Pack the tasks array into an NSData object
    NSData *data = [NSPropertyListSerialization
    dataWithPropertyList:self.tasks
    format:NSPropertyListXMLFormat_v1_0
    options:0
    error];

    // Return the newly-packed NSData object
    return data;
    }

  • (BOOL)readFromData:(NSData *)data
    ofType:(NSString *)typeName
    error:(NSError **)outError
    {
    // This method is called when a document is being loaded
    // You are handed an NSData object and expected to pull our data out of it
    // Extract the tasks
    self.tasks = [NSPropertyListSerialization
    propertyListWithData:data
    options:NSPropertyListMutableContainers
    format:NULL
    error];

    // return success or failure depending on success of the above call
    return (self.tasks != nil);
    }

#pragma mark - Actions

  • (void)addTask:(id)sender
    {
    // If there is no array yet, create one
    if (!self.tasks) {
    self.tasks = [NSMutableArray array];
    }

    [self.tasks addObject:@“New Item”];

    // -reloadData tells the table view to refresh and ask its dataSource
    // (which happens to be this BNRDocument object in this case)
    // for new data to display
    [self.taskTable reloadData];

    // -updateChangeCount: tells the application whether or not the document
    // has unsaved changes, NSChangeDone flags the document as unsaved
    [self updateChangeCount:NSChangeDone];
    }

#pragma mark Data Source Methods

  • (NSInteger)numberOfRowsInTableView:(NSTableView *)tv
    {
    // This table view displays the tasks array,
    // so the number of entries in the table view will be the same
    // as the number of objects in the array
    return [self.tasks count];
    }

  • (id)tableView:(NSTableView *)tableView
    objectValueForTableColumn:(NSTableColumn *)tableColumn
    row:(NSInteger)row
    {
    // Return the item from tasks that corresponds to the cell
    // that the table view wants to display
    return [self.tasks objectAtIndex:row];
    }

  • (void)tableView:(NSTableView *)tableView
    setObjectValue:(id)object
    forTableColumn:(NSTableColumn *)tableColumn
    row:(NSInteger)row
    {
    // When the user changes a task on the table view,
    // update the tasks array
    [self.tasks replaceObjectAtIndex:row withObject:object];

    // And then flag the document as having unsaved changes.
    [self updateChangeCount:NSChangeDone];
    }

@end[/code]

Is the method dataOfType:error: being called?

This can be found out by putting a break point at the start of the method or by simply inserting a log statement:

[code]- (NSData *)dataOfType:(NSString *)typeName
error:(NSError **)outError
{
// This method is called when our document is being saved
// You are expected to hand the caller an NSData object wrapping our data
// so that it can be written to disk

NSLog (@"%s", __func__);
...

}[/code]

Yes, I believe so. I ran the program again with the log and got this:

TahDoodle[7724:568172] -[Document dataOfType:error:]

Then, you probably missed something.

I have created a document based application with a tableview, hooked up the tableview’s delegate and datasource, pasted your code, built the app and ran it - everything worked.

Here is the code I have used:

// Document.m

#import "Document.h"

@interface Document ()
@property (weak) IBOutlet NSTableView    * taskTable;
@property                 NSMutableArray * tasks;
@end

@implementation Document
<Your Code>
@end

Make sure that you did not miss any steps the book is asking you to do.

Also check to make sure that you have not accidentally turned on app sandboxing and disabled file write operations.

[Become a competent programmer faster than you can imagine: pretty-function.org]

I went through the book again and did everything but it still doesn’t seem to work…

How do I check for sandboxing? Sorry, first time noob.

Thank you again for all your help!

(In Xcode Version 6.1 (6A1052d)) In Project and targets list, select your app under Targets. Select Capabilities, check the state of the App Sandbox button - OFF means sandboxing is not active.

Thanks!

I checked app sandbox and it’s turned off. How do I check on the file write operations?

What do you actually get when you save the document (tasks), close the app, run the app again and open the file just saved?

Also, try the following:

  1. Create a document based application, consisting of a single-column table view and an Add Task button;
  2. Replace the contents of your Document.m with the code below;
  3. In the xib file, set up the File’s owner as the delegate and dataSource of the table view; and
  4. Connect the Add Task button the action method (addTask: in Document.m).

This should work.

Document.m

#import "Document.h"

@interface Document ()

@property (weak) IBOutlet NSTableView    * taskTable;
@property                 NSMutableArray * tasks;

@end

@implementation Document
#pragma mark - NSDocument Overrides

- (instancetype)init {
    self = [super init];
    if (self) {
        // Add your subclass-specific initialization here.
    }
    return self;
}

- (void)windowControllerDidLoadNib:(NSWindowController *)aController {
    [super windowControllerDidLoadNib:aController];
    // Add any code here that needs to be executed once the windowController has loaded the document's window.
}

+ (BOOL)autosavesInPlace {
    return YES;
}

- (NSString *)windowNibName {
    // Override returning the nib file name of the document
    // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
    return @"Document";
}

- (NSData *)dataOfType:(NSString *)typeName
                 error:(NSError **)outError
{
    // This method is called when our document is being saved
    // You are expected to hand the caller an NSData object wrapping our data
    // so that it can be written to disk
    // If there is no array, write out an empty array
    if (!self.tasks) {
        self.tasks = [NSMutableArray array];
    }
    
    // Pack the tasks array into an NSData object
    NSData *data = [NSPropertyListSerialization
                    dataWithPropertyList:self.tasks
                    format:NSPropertyListXMLFormat_v1_0
                    options:0
                    error];
    
    // Return the newly-packed NSData object
    return data;
}

- (BOOL)readFromData:(NSData *)data
              ofType:(NSString *)typeName
               error:(NSError **)outError
{
    // This method is called when a document is being loaded
    // You are handed an NSData object and expected to pull our data out of it
    // Extract the tasks
    self.tasks = [NSPropertyListSerialization
                  propertyListWithData:data
                  options:NSPropertyListMutableContainers
                  format:NULL
                  error];
    
    // return success or failure depending on success of the above call
    return (self.tasks != nil);
}

#pragma mark - Actions

- (IBAction)addTask:(id)sender
{
    // If there is no array yet, create one
    if (!self.tasks) {
        self.tasks = [NSMutableArray array];
    }
    
    [self.tasks addObject:@"New Item"];
    
    // -reloadData tells the table view to refresh and ask its dataSource
    // (which happens to be this BNRDocument object in this case)
    // for new data to display
    [self.taskTable reloadData];
    
    // -updateChangeCount: tells the application whether or not the document
    // has unsaved changes, NSChangeDone flags the document as unsaved
    [self updateChangeCount:NSChangeDone];
}

#pragma mark Data Source Methods

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tv
{
    // This table view displays the tasks array,
    // so the number of entries in the table view will be the same
    // as the number of objects in the array
    return [self.tasks count];
}

- (id)tableView:(NSTableView *)tableView
objectValueForTableColumn:(NSTableColumn *)tableColumn
            row:(NSInteger)row
{
    // Return the item from tasks that corresponds to the cell
    // that the table view wants to display
    return [self.tasks objectAtIndex:row];
}

- (void)tableView:(NSTableView *)tableView
   setObjectValue:(id)object
   forTableColumn:(NSTableColumn *)tableColumn
              row:(NSInteger)row
{
    // When the user changes a task on the table view,
    // update the tasks array
    [self.tasks replaceObjectAtIndex:row withObject:object];
    
    // And then flag the document as having unsaved changes.
    [self updateChangeCount:NSChangeDone];
}

@end

If you are still getting nowhere, if you send a zipped copy of your entire project to admin@pretty-function.org, I can take a close look.

With my original project, I would save and then would I go to open it again, it will be blank and nothing would be saved

I tried your code and it saved the number of cells I had, but the text in the cells is not there.

I mean, it’s progress, so thank you for that, but I have no idea what I’m doing wrong. :confused:

Should I send you a zipped copy of my code?

Send it over please.

I sent it over!

I’m running Xcode 6.4 (6E35b) and I’m seeing the same issue. I’ve reviewed the saving/retrieval code multiple times and I can’t seem to figure it out. When I open the xml document that the save created it seems to recognize that there are multiple task objects, but the actual content is not saved anywhere. I log the same error code after attempting to save. Let me know if you have any ideas.

Thanks!

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
    //This method is called when our document is being saved. You are expected to hand the caller an NSData object wrapping our data so that it can be written to disk. If there is no array, write out an empty array.
    if (!self.tasks) {
        self.tasks = [NSMutableArray array];
    }
    
    // Pack the tasks array into an NSData object
    NSData *data = [NSPropertyListSerialization
                        dataWithPropertyList:self.tasks
                                      format:NSPropertyListXMLFormat_v1_0
                                     options:0
                                       error];
    
    NSLog(@"%s", __func__);
    
    //Return the newly-packed object
    return data;
    
    //[NSException raise:@"UnimplementedMethod" format:@"%@ is unimplemented", NSStringFromSelector(_cmd)];
    //return nil;
}

- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError {
    //This method is called when a document is being loaded. You are handed an NSData object and expected to pull our data out of it.
    
    //Extract the tasks
    self.tasks = [NSPropertyListSerialization
                  propertyListWithData:data options:NSPropertyListMutableContainers format:NULL error];
    
    //return success or failure depending on success of the above call
    return (self.tasks != nil);
    
    //[NSException raise:@"UnimplementedMethod" format:@"%@ is unimplemented", NSStringFromSelector(_cmd)];
    //return YES;
}

I was stuck on this for awhile. When you add the NSTableView, make sure its Cell based, not View based. When its view based the method that changes the value in self.tasks when editing (tableView:setObjectValue:forTableColumn:row:) is never called. You can simply select the table view, then attributes inspector and change Content Mode from View based to Cell based, and the text cell edits will be saved.

This is actually called out in https://developer.apple.com/library/prerelease/mac/documentation/Cocoa/Conceptual/TableView/PopulatingCellTables/PopulatingCellTables.html#//apple_ref/doc/uid/10000026i-CH5-SW9, and I read this section a few times, but it never registered because in the document outline the name of the cell was Table Cell View, and there is also a Text Cell, so I thought it was cell based. :blush:

1 Like

Thank you @Childs, it worked for me.

Can’t stand working out all these issues. I bought a new Mac that has OS X Sierra, and oldest Xcode that worked was Xcode 7.3. Lots of compatibility issues.