How to store CCSprite in NSUserDefault - cocoa-touch

I have a strange problem while working with CCSprite subclass Creature.
Lets,my object is Creature* creature;
The class Creature declaration-
#interface Creature : CCSprite <NSCoding>{
int creatureAge;
NSString *creatureName;
}
Implementation
+(id)initializeCreatureWithType:(int)type
{
Creature *creature = nil;
creature = [[[super alloc] initWithFile:[NSString stringWithFormat:#"ch%i_default.png",type]]autorelease];
return creature;
}
The problem is when i store my Creature class object 'creature' in NSUserDefault using-
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.creatureName forKey:#"creatureName"];
[encoder encodeInt:self.creatureAge forKey:#"creatureAge"];
}
And the decode it with-
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
self.creatureName = [decoder decodeObjectForKey:#"creatureName"];
self.creatureAge= [decoder decodeIntForKey:#"creatureAge"];
}
Then save the creature object using-
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:creature];
[defaults setObject:myEncodedObject forKey:#"my_pet"];
And the load-
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [defaults objectForKey:#"my_pet"];
Creature* newcreature = (Creature *)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
The problem is that when I load this i get the property value of previously stored creature, but the image that is assigned to previous creature perhaps does not copied. Because if i add the newcreature to any CCLayer it does not display any image, though it get the property value of previous creature.
What can I do now to get the newcreature with image? is it needed to add image name as a separate property???

You could simply store the type as well and then do it like this:
#interface Creature : CCSprite {
int creatureType;
int creatureAge;
NSString *creatureName;
}
+ (id)creatureWithType:(int)type;
- (id)initWithCreatureType:(int)type;
#end
#implementation Creature
+ (id)creatureWithType:(int)type
{
return [[[[self class] alloc] initWithCreatureType:type] autorelease];
}
- (id)initWithCreatureType:(int)type
{
self = [super initWithFile:[NSString stringWithFormat:#"ch%i_default.png", type]];
if (!self) return nil;
creatureType = type;
return self;
}
- (id)initWithCoder:(NSCoder *)decoder {
int type = [decoder decodeIntForKey:#"creatureType"];
self = [self initWithCreatureType:type];
if (!self) return nil;
self.creatureName = [decoder decodeObjectForKey:#"creatureName"];
self.creatureAge= [decoder decodeIntForKey:#"creatureAge"];
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeInt:creatureType forKey:#"creatureType"];
[encoder encodeObject:self.creatureName forKey:#"creatureName"];
[encoder encodeInt:self.creatureAge forKey:#"creatureAge"];
}
#end
You might want to expose creatureType via a property as well. Note that instead of initializeCreatureWithType: it's "more Cocoa" to use the name creatureWithType:.

Related

Archive/Unarchive Parent-Child objects with a key

I have a class A and a subclass B:
#interface A:NSObject<NSCoding>
String a;
String b;
#end
#interface B: A<NSCoding>
int c;
// `a` and `b` attributes are inherited
#end;
I want to archive some objects of A and some of B using attribute a as key.
The encoding and decoding protocols in both class have been defined as:
#implementation A
...
- (void) encodeWithCoder:(NSCoder *)encoder {
//[super encodeWithCoder:encoder];
[encoder encodeObject:self forKey:a];
}
- (id)initWithCoder:(NSCoder *)decoder {
// self = [super initWithCoder:decoder];
if (self=[super init]) {
self = [decoder decodeObjectForKey:a];
}
return self;
}
#end
#implementation B
..
- (void) encodeWithCoder:(NSCoder *)encoder {
[super encodeWithCoder:encoder];
[encoder encodeObject:self forKey:super.a];
}
- (id)initWithCoder:(NSCoder *)decoder {
//self = [super initWithCoder:decoder];
if (self) {
self = [decoder decodeObjectForKey:super.a];
}
return self;
}
For some object list dataArray = [A("One"), B("Two")], the archiving logic is
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dataArray];
NSError* error = nil;
BOOL success = [data writeToFile: #"xyz.dat" options:0 error: &error];
if (!success)
NSLog( #"error = %#", [error description] );
Again reading from the archived data:
NSData *lD = [[NSData alloc] initWithContentsOfFile:#"xyz.dat"];
NSKeyedUnarchiver *arc = [[NSKeyedUnarchiver alloc] initForReadingWithData:lD];
Now for the object with the attribute a as"One"
While trying to read the object back from archived data, i am getting null!
NSLog(#"Value - %#",[arc decodeObjectForKey:#"One"]);
Maybe,the problem was with encoding self.
I did the following changes:
#implementation A
...
- (void) encodeWithCoder:(NSCoder *)encoder {
if(self.a!=nil)
[encoder encodeObject:self.a forKey:#"a"];
if(self.b!=nil)
[encoder encodeObject:self.b forKey:#"b"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self=[super init];
if (self!=nil) {
self.a= [decoder decodeObjectForKey:#"a"];
self.b= [decoder decodeObjectForKey:#"b"];
}
return self;
}
#end
#implementation B
..
- (void) encodeWithCoder:(NSCoder *)encoder {
[super encodeWithCoder:encoder ];
[encoder encodeObject:self.c forKey:#"c"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if ( (self = [super initWithCoder:decoder ]) )
{
self.c= [decoder decodeObjectForKey:#"c"];
}
return self;
}
While encoding data:
NSMutableDictionary *dict = [NSMutableDictionary new];
[dict setObject:[[A alloc]init()] forKey:#"One"];//say
[dict setObject:[[B alloc]init()] forKey:#"Two"];//say
NSMutableData *yourData = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:yourData];
for (NSString* key in dict) {
id value = [dict objectForKey:key];
[archiver encodeObject:value forKey: key];
}
[ archiver finishEncoding];
[yourData writeToFile:#"xyz.dat" atomically:YES];
[yourData release];
[archiver release];
And decoding:
NSData *data = [[NSData alloc] initWithContentsOfFile:#"xyz.dat"];
NSKeyedUnarchiver *keyArc= [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSLog(#"%#",[keyArc decodeObjectForKey:#"One"]);
NSLog(#"%#",[keyArc decodeObjectForKey:#"Two"]);
[keyArc finishDecoding];
Ref: https://stackoverflow.com/a/10929284/2301570

Loading data once the data is in array

[array addObject:textdata.text];
NSUserDefaults *save = [NSUserDefaults standardUserDefaults];
[save setObject:array forKey:#"success" ];
[save synchronize];
-(void) viewDidLoad
NSUserDefaults *viewdata1 = [NSUserDefaults standardUserDefaults];
[viewdata1 objectForKey:#"success"];
[viewdata1 synchronize];
[tabledata reloadData];
Once the data is saved in the array, how do I upload it once the app runs again? I want the data to load back in the table once.
The first step is to retrieve it from user defaults. The second step is not to drop it on the floor.
[viewdata1 objectForKey:#"success"];
This does one, but not the other: You retrieve it, but then you drop it on the floor.
You need to store the object as the value of a property (which means you will need to declare a property for that purpose), then, in your table view's data source, return the count of that array as your number of rows and objects in the array (or properties of those objects) as the row values.
Also, you shouldn't need to call synchronize, especially after retrieving the value.
You should make like this:
TSTableViewController.h:
#property(nonatomic, readwrite, retain) NSMutableArray* dataSource;
TSTableViewController.m:
- (id) init
{
if ((self = [super init]))
{
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(applicationDidEnterBackground:)
name: UIApplicationDidEnterBackgroundNotification
object: nil];
}
return self;
}
- (void) applicationDidEnterBackground: (NSNotification*) notification
{
[[NSUserDefaults standardUserDefaults] setObject: self.dataSource
forKey: #"success" ];
}
- (void) viewDidLoad
{
[super viewDidLoad];
NSArray* array = [[NSUserDefaults standardUserDefaults] objectForKey: #"success"];
if (array)
{
self.dataSource = [NSMutableArray arrayWithArray: array];
}
else
{
self.dataSource = [[[NSMutableArray alloc] init] autorelease];
}
[tableView reloadData];
}
- (void) addDataToDataSource
{
[self.dataSource addObject: textdata.text];
[tabledata reloadData];
}
- (void) dealloc
{
[dataSource release];
dataSource = nil;
[super dealloc];
}

NSKeyedArchiver archivedDataWithRootObject: does not call encodeWithCoder

I cant get the archivedDataWithRootObject method to call encodeWithCoder, it seems pretty simple from what I understand but it doesn't work.
Here's the code:
#import <Foundation/Foundation.h>
#interface Details : NSObject <NSCoding>{
NSMutableArray *myArray;
}
#property(nonatomic,retain) NSMutableArray *myArray;
#end
#import "Details.h"
#implementation Details
#synthesize myArray;
-(id)init{
[super init];
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
NSLog(#"initWithCoder");
if (self = [super init])
{
self.myArray = [[aDecoder decodeObjectForKey:#"details"]retain];
}
return self;
}
- (NSMutableArray*)getDetails{
NSLog(#"getDetials");
// NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *details = [[NSUserDefaults standardUserDefaults] objectForKey:#"details"];
if (details != nil){
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:details];
if (oldSavedArray != nil)
self.myArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
else
self.myArray = [[NSMutableArray alloc] initWithCapacity:1];
}
return self.myArray;
}
- (void) addDetails:(NSMutableArray *)details{
NSLog(#"add Details");
[self.myArray addObject:details];
[NSKeyedArchiver archivedDataWithRootObject:self.myArray];
}
- (void) encodeWithCoder:(NSCoder *)coder;
{
NSLog(#"encodeWithCoder");
[coder encodeObject:self.myArray forKey:#"details"];
}
#end
NSLog(#"encodeWithCoder") doesn't run.
I might be missing something, If anyone has any ideas it would be appreciated.
[NSKeyedArchiver archivedDataWithRootObject:self.myArray];
You are telling the NSKeyedArchiver to archive the myArray variable, not the object you have just implemented <NSCoding> with!
Also that method returns NSData * which you then have to write to disk separately if needed.
Try this:
NSData * archivedData = [NSKeyedArchiver archivedDataWithRootObject:self];

Init a NSMutableArray in a singelton that saves as a state

Im am using a pattern from the book Learning Cocos2D. I have a GameState class that is a singleton that can be saved as a state. The BOOL soundOn gets init as false the first time the class gest created, but the array levelProgress dosent. I made comments on all the lines on which I have tried to init the array.
The class uses a helper that loads and saves the data.
When i try 1 or 2 i get "instance variable 'levelProgress' accessed in class method" error.
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface GameState : NSObject <NSCoding> {
BOOL soundOn;
NSMutableArray *levelProgress;
}
+ (GameState *) sharedInstance;
- (void)save;
#property (assign) BOOL soundOn;
#property (nonatomic, retain) NSMutableArray *levelProgress;
#end
#import "GameState.h"
#import "GCDatabase.h"
#implementation GameState
#synthesize soundOn;
#synthesize levelProgress;
static GameState *sharedInstance = nil;
+(GameState*)sharedInstance {
#synchronized([GameState class])
{
if(!sharedInstance) {
sharedInstance = [loadData(#"GameState") retain];
if (!sharedInstance) {
[[self alloc] init];
// 1. levelProgress = [[NSMutableArray alloc] init];
}
}
return sharedInstance;
}
return nil;
}
+(id)alloc
{
#synchronized ([GameState class])
{
NSAssert(sharedInstance == nil, #"Attempted to allocate a \
second instance of the GameState singleton");
sharedInstance = [super alloc];
// 2. levelProgress = [[NSMutableArray alloc] init];
return sharedInstance;
}
return nil;
}
- (void)save {
saveData(self, #"GameState");
}
- (void)encodeWithCoder:(NSCoder *)encoder {
// 3. levelProgress = [[NSMutableArray alloc] init];
[encoder encodeBool:currentChoosenCountry forKey:#"soundOn"];
[encoder encodeObject:levelProgress forKey:#"levelProgress"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super init])) {
// 4. levelProgress = [[NSMutableArray alloc] init];
soundOn = [decoder decodeBoolForKey:#"soundOn"];
levelProgress = [decoder decodeObjectForKey:#"levelProgress"];
}
return self;
}
#end
Solution:
*I just added av init method...*
-(id)init {
self = [super init];
if (self != nil) {
levelProgress = [[NSMutableArray alloc] init];
}
return self;
}
Try this (this code may contain syntax or logic errors - I wrote it in notepad):
#interface GameState : NSObject <NSCoding>
#property (nonatomic, readwrite) BOOL soundOn;
#property (nonatomic, retain) NSMutableArray *levelProgress;
+ (GameState *)sharedState;
- (void)writeDataToCache;
#end
//
#implementation GameState
#synthesize soundOn, levelProgress;
#pragma mark - Singleton
static GameState *sharedState = nil;
+ (void)initialize {
static BOOL initialized = NO;
if (!initialized) {
initialized = YES;
if ([[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:#"%#/gameState", pathCache]]) {
NSData *encodedObject = [[NSData alloc] initWithContentsOfFile:[NSString stringWithFormat:#"%#/gameState", pathCache]];
data = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
}
else
data = [[GameState alloc] init];
}
}
+ (GameState *)sharedState {
return sharedState;
}
#pragma mark - Initialization
- (id)init {
if (self = [super init]) { // will be inited while application first run
soundOn = NO;
levelProgress = [[NSMutableArray alloc] init];
return self;
}
return nil;
}
#pragma mark - Coding Implementation
- (void)writeDataToCache { // use this method to save current state to cache
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:self];
if([[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:#"%#/GameState", pathCache]])
[[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:#"%#/GameState", pathCache] error:nil];
[[NSFileManager defaultManager] createFileAtPath:[NSString stringWithFormat:#"%#/GameState", pathCache] contents:encodedObject attributes:nil];
NSLog(#"GameState was saved successfully.");
}
- (void)encodeWithCoder:(NSCoder*)encoder {
[encoder encodeBool:self.soundOn forKey:#"soundOn"];
[encoder encodeObject:self.levelProgress forKey:#"levelProgress"];
}
- (id)initWithCoder:(NSCoder*)decoder {
if ((self = [super init])) {
self.soundOn = [decoder decodeBoolForKey:#"soundOn"];
self.levelProgress = [decoder decodeObjectForKey:#"levelProgress"];
NSLog(#"GameState was inited successfully");
return self;
}
return nil;
}
#end
1 & 2 are the same (and should not work, since levelProgress is an instance variable), 3 and 4 should be encoding/decoding the array, not initializing it anew.
1 and 2 are a mistake - you can't access instance variable without specifying an instance.
You can fix 1 by changing the line to sharedInstance.levelProgress = ....
2 is just a bad idea, you're breaking common convention of initializing using init... methods, it may surprise and cause problems for an other programmer who'll be working on the same code later.
3 and 4 are ok but if loadData fails they will not be executed and the object will be initialized with ordinary init and the array will be nil pointer:
[[self alloc] init];
you should also override init and initialize the properties there.

Storing NSMutableArray of UIViews in NSUserDefaults

I have read several posts about this but I'm not able to fix the error. If someone could please help.
Here is the code I use. I have an NSMutableArray called list.
-(void) awakeFromNib
{
prefs=[[NSUserDefaults standardUserDefaults]retain];
if ([prefs arrayForKey:#"list"]) {
list=[[NSMutableArray alloc]initWithArray:[prefs objectForKey:#"list"]];
}
else {
list=[[NSMutableArray alloc]init];
}
}
-(void)saveData
{
NSLog(#"saving data!");
[prefs setObject:list forKey:#"list"];
}
- (void)dealloc {
[self saveData];
[prefs synchronize];
[prefs release];
}
You cannot store UIView instances in the user defaults, but only objects that can be serialized in a property list (see here) Also, as #Justin said, do not retain or release the defaults object.
Thank you. However, I had earlier read that you cannot save an NSMutableArray in NSUserDefaults so I attempted to convert it to NSData and then use it.
Here's my ViewController.m file:
-(id)initWithCoder:(NSCoder *)aDecoder
{
self=[[calViewController alloc]init];
if (self!=nil) {
self.list=[aDecoder decodeObjectForKey:#"list"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:list forKey:#"list"];
}
And here's my AppDelegate.m file:
-(void) applicationDidFinishLaunching:(UIApplication *)application
{
NSLog(#"Application did finish launching");
defaults=[NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray=[defaults objectForKey:#"lastArray"];
if (dataRepresentingSavedArray!=nil) {
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if (oldSavedArray != nil)
{
listAr = [[NSMutableArray alloc] initWithArray:oldSavedArray];
else
{
listAr = [[NSMutableArray alloc] init];
NSLog(#"listAr: %#",listAr);
}
}
}
-(void) applicationWillTerminate:(UIApplication *)application
{
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:listAr] forKey:#"lastArray"];
}
I've never used UserDefaults before and I absolutely confused. I've read all the docs but I don't seem to be getting it right!
EDIT:
-(void) applicationDidFinishLaunching:(UIApplication *)application
{
NSLog(#"Application did finish launching");
defaults=[NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"lastArray"]) {
NSData *dataRepresentingSavedArray=[defaults objectForKey:#"lastArray"];
if (dataRepresentingSavedArray!=nil) {
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if (oldSavedArray != nil)
listAr = [[NSMutableArray alloc] initWithArray:oldSavedArray];
}
}
else
{
listAr = [[NSMutableArray alloc] init];
NSLog(#"listAr: %#",listAr);
}
}