7

I have this mutable array named myWallet with its property declaration:

@property (nonatomic, strong) NSMutableArray *myWallet;

This array is being filled up by Cards, which is an object. I create a new instance of a Card from another View Controller so I use a delegate method in able to pass this created card on my initial View Controller which is a Table View Controller. Below is the delegate method

- (void)addCardViewController:(AddCardViewController *)sender didCreateCard:(Card *)newCard
{
    // insert a new card

    self.myCard = newCard;
    [self.myWallet addObject:self.myCard];

    [self.tableView reloadData];
}

So far, I can create new instances of Card, and when I do, a new table cell appears in my initial view controller (Card Wallet VC).

What I want is when the user is done using the app, the created instance(s) of Card he / she previously created still appears on next run time of the app. So I used NSUserDefaults to do this. The method below is how I'm using NSUserDefaults to save.

- (void)saveMyWallet
{

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    [defaults setObject:self.myWallet forKey:@"myWalletArray"];

    [defaults synchronize];
    NSLog(@"I am saved");
}

I call this method inside the didSelectRowAtIndexPath. When I run the app, then create a new instance of a Card, click the table cell, this appears on my log.

2012-05-04 14:51:00.900 CardWallet[24998:f803] *** -[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '("<Card: 0x6aa02f0>")' of class '__NSArrayM'.  
Note that dictionaries and arrays in property lists must also contain only property values.

What could be a possible reason to this? And can you suggest me ways I can go about it.

Thanks in advance.

Grauzten
  • 353
  • 5
  • 19
  • First remove property of your array and then check what values have in your array using nslog that go for key – vishiphone May 04 '12 at 07:02

3 Answers3

4

Your Card class must also conform to the NSCoding Protocol because you can only store NSData, NSString, NSArray, NSDictionary and NSNumber with NSUserDefaults. That means, you need to add two methods to your class:

- (void)encodeWithCoder:(NSCoder *)enCoder {
    [super encodeWithCoder:enCoder];

    [enCoder encodeObject:instanceVariable forKey:INSTANCEVARIABLE_KEY];

    // Similarly for the other instance variables.
    ....
}

- (id)initWithCoder:(NSCoder *)aDecoder {

   if(self = [super initWithCoder:aDecoder]) {
       self.instanceVariable = [aDecoder decodeObjectForKey:INSTANCEVARIABLE_KEY];

       // similarly for other instance variables
       ....
   }

   return self;
}

Then create a NSData NSKeyedArchiver before storing it to NSUserDefaults. Like this:

 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
 NSData *yourArrayAsData = [NSKeyedArchiver archivedDataWithRootObject:yourArray];
 [ud setObject:sharesAsData forKey:kA_KEY_FOR_USERDEFAULTS];
Jonas Schnelli
  • 9,965
  • 3
  • 48
  • 60
  • 1
    That is not enough, NSUserDefaults will not automatically encode them for you. You need to do so yourself! – unexpectedvalue May 04 '12 at 06:59
  • Question, on my card Class, I have 3 instance variables: name, pin, and points. Given that, does it mean that I should also decode and encode 3 times? – Grauzten May 04 '12 at 07:12
  • You then need to have 3 times `encodeObject:forKey:` and 3 times `decodeObjectForKey;` yes. If your variables are not objects (could be a int or float), then use `encodeInt:forKey:` and `decodeIntForKey:` – Jonas Schnelli May 04 '12 at 07:14
  • Ok. Ahm an error says " No visible @interface for 'NSObject' declare the selector 'encodeWithCoder' " on the line `[super encodeWithCoder:enCoder];` And I declared the methods inside the Card.h `- (void) encodeWithCoder:(NSCoder *)enCoder;` `- (id) initWithCOder: (NSCoder *)aDecoder;` – Grauzten May 04 '12 at 07:22
  • Note to go the other way, it's `[NSKeyedUnarchiver unarchiveObjectWithData:data]`. – devios1 Aug 13 '13 at 06:21
4
@implementation Card

- (void)encodeWithCoder:(NSCoder *)enCoder 
{
    [super encodeWithCoder:enCoder];
    [enCoder encodeObject:self.name forKey:@"CardName"];
    [enCoder encodeInt:self.pin forKey:@"CardPin"];
    [enCoder encodeDouble:self.points forKey:@"CardPoints"];
}

- (id)initWithCoder:(NSCoder *)aDecoder 
{
   if(self = [super initWithCoder:aDecoder]) 
   {
       self.name = [aDecoder decodeObjectForKey:@"CardName"];
       self.pin = [aDecoder decodeIntForKey:@"CardPin"];
       self.points = [aDecoder decodeDoubleForKey:@"CardPoints"];      
   }
   return self;
}

@end

Assumed that name is of type NSString, pin is of type NSInteger and Points is of type Double you can use above code directly in your project.

Janak Nirmal
  • 22,706
  • 18
  • 63
  • 99
0

If the array self.myWallet contains objects that aren't property list objects you will need to encode it yourself to NSData before saving, and decode when loading.

Check out the answer to that question.

Community
  • 1
  • 1
unexpectedvalue
  • 6,079
  • 3
  • 38
  • 62
  • Thanks! I've gone through this same answer, I've tried it and there still exist an error occurring during run time. I'll try the other suggestion above, where you commented also :) – Grauzten May 04 '12 at 07:09