1

I am developing todo list app for iOS. I have a custom class called ToDoItem. My design is to write the users contents to a text file and read it back from the file when the user again opens the app. I have implemented the methods that confirms to NSCoding protocol for my ToDoItem class.

Method for writing the data is given below

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

// if cancel button is pressed
if (sender!=self.saveBtn) {
    return;
}
// if save button is pressed
else{
    if (self.addField.text.length>0) {
        ToDoItem *item = [[ToDoItem alloc] init];
        item.itemName = self.addField.text;
        item.isDone = FALSE;
        item.row = [ToDoListTableTableViewController getCount];
        self.toDoItem = item;


        NSMutableArray *arr = [NSMutableArray arrayWithContentsOfFile:_appFile];
        [arr addObject:item];

        NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arr];

        NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:_appFile];

        [handle seekToEndOfFile];
        [handle writeData:data];
        [handle closeFile];

    }
}

Method for reading the data is given below

- (void)loadData{
if ([[NSFileManager defaultManager] fileExistsAtPath:_appFile]) {

    NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:_appFile];

    if (handle) {
        NSData *data = [handle readDataToEndOfFile];
        if (data) {
            ToDoItem *item;
            NSMutableArray *arr = [NSKeyedUnarchiver unarchiveObjectWithData:data];

            for (item in arr) {
                [_toDoItems addObject:item];
            }
        }
        [handle closeFile];
    } 
}}

Now I am able to write only single ToDoItem to file and able to read it. If I write more than one object of ToDoItem, while reading it back I get "[NSKeyedUnarchiver initForReadingWithData:]: incomprehensible archive " exception. My concern is I just want to append my ToDoItem objects as and when the user saves the data and retrieve all the information again when the user relaunches the app.

NSCoding methods

- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:_itemName forKey:ITEMNAME];
[aCoder encodeObject:_date forKey:DATE];
[aCoder encodeBool:_isDone forKey:DONE];
[aCoder encodeInteger:_row forKey:ROW];
}

- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super init];
if (self) {
    _itemName = [aDecoder decodeObjectForKey:ITEMNAME];
    _date = [aDecoder decodeObjectForKey:DATE];
    _isDone = [aDecoder decodeBoolForKey:DONE];
    _row = [aDecoder decodeIntegerForKey:ROW];
}
return self;
}

Thanks in advance

Chandan
  • 394
  • 6
  • 16
  • Please show your `NSCoding` methods for your `ToDoItem` class. By the way, in the second code segment, once it works `arr` will be an array of `ToDoItems`, so you don't need to iterate through and add the objects to the array - you can simply assign `arr` to `self.toDoItems` – Paulw11 Jun 29 '15 at 06:54
  • @Paulw11 I have added the NSCoding methods. Also I tried directly setting array [_toDoItems arrayByAddingObjectsFromArray:arr]. But Still facing the same error – Chandan Jun 29 '15 at 07:07
  • @Paulw11 If the file contains more than one item then second code segment will throw exception. Thats the place where I am getting stucked – Chandan Jun 29 '15 at 07:08
  • Sorry, I just looked more closely at your save method - You cannot append to the file. You need to replace the file each time. – Paulw11 Jun 29 '15 at 07:20
  • @Paulw11 You mean to say every time I have to overwrite the entire contents?. Even If i Do that I am facing problem when I am unarchiving the data for reading i.e reading when there are more than one item. Can you help me there – Chandan Jun 29 '15 at 07:24
  • Core Data is probably a better solution, but as long as you write/read the full array every time it should work - see my answer – Paulw11 Jun 29 '15 at 07:25
  • I will implement that solution and let you know – Chandan Jun 29 '15 at 07:26

2 Answers2

1

You cannot append new data to the end of the file when you add a new item - you need to completely replace the file each time.

You need to add the new item to your existing array of items and then save the archive of the full array. It is probably cleaner if your new todo item view controller simply returns the new item or nil and have the save done in the "outer" VC

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

// if cancel button is pressed
if (sender!=self.saveBtn) {
    return;
}
// if save button is pressed
else{
    if (self.addField.text.length>0) {
        ToDoItem *item = [[ToDoItem alloc] init];
        item.itemName = self.addField.text;
        item.isDone = FALSE;
        item.row = [ToDoListTableTableViewController getCount];
        self.toDoItem = item;

        [self.toDoItems addObject:item];  // This needs to be a reference to the array that holds all of your todo items

        NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.toDoItems];

        NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:_appFile];

        [handle truncateFileAtOffset:0];
        [handle writeData:data];
        [handle closeFile];

    }
}
Paulw11
  • 108,386
  • 14
  • 159
  • 186
1

I'd recommend to use a property list file rather than a text file. This is a solution to load and save the todoItems in a plist file.

Assumptions:

_appFile : an NSString variable containing the path to the plist file

__toDoItems : an initialized NSMutableArray variable to hold the todo items

Both methods use NSPropertyListSerialization which provides more read and write options

- (void)loadData
{
    NSDictionary *prefs = nil;
    NSData *plistData = [NSData dataWithContentsOfFile:_appFile];
    if (plistData) {
        prefs = (NSDictionary *)[NSPropertyListSerialization
                                        propertyListWithData:plistData
                                        options:NSPropertyListImmutable
                                        format:NULL
                                        error:nil];
    }
    if (prefs) {
        NSArray *itemDataArray = prefs[@"todoItems"];
        if (itemDataArray) {
            [_toDoItems removeAllObjects]; // might be not needed
            for (itemData in itemDataArray) {
                 [_toDoItems addObject:(ToDoItem *)[NSKeyedUnarchiver unarchiveObjectWithData:itemData]];
            }
        }
    }
}


- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

    // if cancel button is pressed
    if (sender != self.saveBtn) {
        return;
    }
    // if save button is pressed
    else {
        NSMutableArray *itemDataArray = [[NSMutableArray alloc] init];
        for (ToDoItem *item in _toDoItems) {
            [itemDataArray addObject:[NSKeyedArchiver archivedDataWithRootObject:item]];
        }
        NSError *error = nil;
        NSDictionary *prefs = @{@"todoItems" : itemDataArray};
        NSData *plistData = [NSPropertyListSerialization dataWithPropertyList: prefs
                                                         format:NSPropertyListBinaryFormat_v1_0
                                                        options:0 
                                                          error:&error];

        if (plistData) {
         [plistData writeToFile:_appFile atomically:YES];
     }
        else {
        // do error handling
     }
}
vadian
  • 274,689
  • 30
  • 353
  • 361