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];
}
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
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.
(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.
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:
Create a document based application, consisting of a single-column table view and an Add Task button;
Replace the contents of your Document.m with the code below;
In the xib file, set up the File’s owner as the delegate and dataSource of the table view; and
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.
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.
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.