storing custom_button in NSUserdefaults issue? - iphone

i am storing my "custom button" in NSUserdefaults using the following code.But i am getting an error "[UIImage encodeWithCoder:]: unrecognized selector sent to instance" while converting the object to NSdata..here "custom button" is UIButton class. Anyone know why...? please help me.
Custom_button *lock11 = (Custom_button*)[menu1 viewWithTag:100];
NSLog(#"opened lock1 ========= %#",lock11);
lock11.is_menu_lock_opened = YES;
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:lock11]; //[NSKeyedArchiver archivedDataWithRootObject:lock11];
[prefs setObject:myEncodedObject forKey:#"set1lock"];

The NSUserDefaults class provides convenience methods for accessing
common types such as floats, doubles, integers, Booleans, and URLs. A
default object must be a property list, that is, an instance of (or
for collections a combination of instances of): NSData, NSString,
NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any
other type of object, you should typically archive it to create an
instance of NSData.
Objects that don't map directly to property list objects are turned into NSData by being sent a coder and encoding their contents. [UIImage encodeWithCoder:]. They need to conform to the NSCoding protocol for this to work. You will find that UIImage does not confirm to the NSCoding protocol before iOS 5. If you want to deploy before iOS 5 you will have to work something out yourself, by implementing NSCoding in your Custom Button class and storing that image in a different way.

Implement this method into your Custom_button Class
initWithCoder and encodeWithCoder for all object
-(id) initWithCoder: (NSCoder *)coder {
self = [[CastInnerListData alloc] init];
if (self != nil) {
self.object1 = [coder decodeObjectForKey:#"object1"];
self.object2 = [coder decodeObjectForKey:#"object2"];
}
return self;
}
-(void) encodeWithCoder: (NSCoder *)coder{
[coder encodeObject:object1 forKey:#"object1"];
[coder encodeObject:object2 forKey:#"object2"];
}
For more detail click here

Well a little searching would have given you the answer fast:
UIImage encodeWithCoder urecognised selector?

Related

Saving multiple map annotations - crashes

I have been searching top and bottom for info on how to do this. I landed on a great tutorial! So I am still quite newbie to this. Basically I have been trying to store Map View annotations to an array. The annotations are a separate class which basically overrides / acts as a MKAnnotation for a pin annotation. It has three properties:
Annotation Coordinate
Annotation Title
Annotation Subtitle
This array needs to store into the NSUserDefaults. I faced a problem, here is the log:
[UIMutableIndexPath setObject:forKey:]: unrecognized selector sent to
instance 0x1187b0
My Annotation class objects stored inside the array could not be saved to the user defaults. So I had to turn this array into NSData and then save it, right?
I have a lot of code setup, just not working. Here is how I attempt all of this:
View Controller Class.m:
- (void)syncMap { // this method is called in viewWillDissapear (for running tests) and applicationDidEnterBackground in App Delegate
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject: localOverlays]; // this is the array I was talking about
[defaults setObject:data forKey:#"overlays"];
[defaults synchronize];
}
- (void)initCircles { // called in AppDelegate UIApplicationDelegate method: applicationDidFinishLaunchingWithOptions
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey: #"overlays"];
localOverlays = [NSKeyedUnarchiver unarchiveObjectWithData: data];
if (!localOverlays) {
// Either there is a problem or it is the first time opening the app
localOverlays = [[NSMutableArray alloc] init];
}
}
NOTE: I AM TESTING WITH TWO ANNOTATIONS IN THE ARRAY (localOverlays)
So, my localOverlays can be encoded / archived(using NSCoder) since it is an NSArray. However, I had to add some further setup in my Annotation class. In its .h it uses to NSCoding and MKAnnotation: like the following< NSCoding, MKAnnotation>. Sorry if I am not using the correct term. Here is my .m:
- (void)encodeWithCoder:(NSCoder *)aCoder { // should only be called when app enters background state, but since that cannot log in the console, like before I set it up so it should also be called in viewWillDissapear
NSLog(#"encodeCoder called in Annotation"); // gets called twice when the view will disappear... GOOD!
[aCoder encodeObject: title forKey: #"title"];
[aCoder encodeObject: subtitle forKey: #"subtitle"];
[aCoder encodeDouble: coordinate.latitude forKey: #"latitude"];
[aCoder encodeDouble: coordinate.longitude forKey: #"longitude"];
}
- (id)initWithCoder:(NSCoder *)aDecoder { // Should be called only at startup of app (not the first time you startup the app though... because data will be NULL)
NSLog(#"In the Annotation class, initWithCoder is called"); // Does get called at appropriate times twice... NICE!
self = [super init];
if (self) {
title = [aDecoder decodeObjectForKey: #"title"];
subtitle = [aDecoder decodeObjectForKey: #"subtitle"];
double lat = [aDecoder decodeDoubleForKey: #"latitude"];
double lon = [aDecoder decodeDoubleForKey: #"longitude"];
coordinate = CLLocationCoordinate2DMake(lat, lon);
}
return self;
}
So as you can see, I have everything setup for archiving, right? Well it seems not... because now in the .m of the ViewController, I also have this code in viewDidLoad:
for (Annotation *pin in localOverlays) {
if (pin) {
NSLog(#"valid pin: _CMD updateCircles");
[mapView addAnnotation: pin];
}
}
This code works nicely and fine the first time I open my app and add the pins. Okay, so now I have exited the view and quit the app, and deleted from multitasking bar. When I open it back up, I get a crash at the fast enumeration line:
-[NSURL countByEnumeratingWithState:objects:count:]: unrecognized selector sent to instance 0xcd31140
So, there is something wrong with all my archiving and encoding setup. What is wrong here... I know this was a lengthy question but I tried to structure it well. Am I setting up my code totally incorrect, is there a typo/bug in my code. Thanks all!
UPDATE:
I have got the coordinate variable encoded, therefore when I startup the app after the pin appear at the right coordinate, but when I try to press it to see the title and subtitle, I get the following crash:
objc_msgSend
So something is released right... just a guess... bad memory management? What would cause this crash in my code?
UPDATE:
I have looked deeper and further into my code and changed a few release statements around, just improved my memory management and done a bit of optimization. Now I get a more specific crash:
*** -[CFString length]: message sent to deallocated instance 0x147490
So my title or subtitle IS deallocated... why? I have checked my code it should be absolutely fine, especially since the coordinates are fine...
UPDATE:
I have solved this problem! I came to the realization, the coordinate two variables, latitude and longitude are doubles, data types... NOT objects! Therefore they are sticking on and working only BECAUSE they are copied... unlike objects which are references. Long story short I needed to retain. Just like this:
title = [[aDecoder decodeObjectForKey: #"titler"] retain];
subtitle = [[aDecoder decodeObjectForKey: #"subtitler"] retain];
I'm not sure what exactly is the reason for the crashes. But I noticed some mistakes in your encode/decode methods:
EDIT: Only if your super class conforms to NSCoding:
In encodeWithCoder you should at first call:
[super encodeWithCoder: aCoder];
In initWithCoder replace
self = [super init];
by
self = [super initWithCoder:aDecoder]
The following returns an immutable object (not NSMutableArray):
localOverlays = [NSKeyedUnarchiver unarchiveObjectWithData: data];
Replace this line by:
localOverlays = [[NSKeyedUnarchiver unarchiveObjectWithData: data] mutableCopy];
I hope that helps!
I have solved this problem! I came to the realization, the coordinate two variables, latitude and longitude are doubles, data types... NOT objects! Therefore they are sticking on and working only BECAUSE they are copied... unlike objects which are references. Long story short I needed to retain. Just like this:
title = [[aDecoder decodeObjectForKey: #"titler"] retain];
subtitle = [[aDecoder decodeObjectForKey: #"subtitler"] retain];

IOS : How to Copy One User Define Model Class to Another Object of It's type

i have a problem in Coping a Object
I created a Model Class and use the Object Where i required it.
Certainly i required a copy of it for Some Reason
so i use in the following manner
Modeldata *copyOfDate=[[[Modeldata alloc]init ]autorelease];
copyOfDate=[g_Data copy];//This Line Gives me a Exception .
i think i need to OVERRIDE the copy Method and assignment Operator.
Please provide Me a solution for it, i am waiting for the Quick Response
You need to override copyWithZone method in your model class implementation as follows.
#implementation Modeldata
-(id) copyWithZone: (NSZone *) zone
{
Modeldata *objModeldata = [[Modeldata allocWithZone:zone] init];
objModeldata.yourProperty = yourProperty;
objModeldata.yourProperty1 = yourProperty1; //All your properties
return objModeldata;
}
#end
Now if you do following will work
Modeldata *copyOfDate=[g_Data copy];
You don't need to allocate memory there by writing
Modeldata *copyOfDate=[[[Modeldata alloc]init ]autorelease];

Initializing a static singleton object with NSCoder

I'm working on an iPhone app and facing some troubles with my shared singleton class.
I'm using a shared singleton to store two variables
int gameRuns and int totalScore
'gamRuns' just increments every time the user loads the app, and 'totalScore' is obvious :D
the issue is as follows, I load the singleton and init using my own method when the app loads using this code:
+ (SingletonLevelState*)sharedLevelStateInstance {
static SingletonLevelState *sharedLevelStateInstance;
#synchronized(self) {
if(!sharedLevelStateInstance) {
//Init a singleton
sharedLevelStateInstance = [[SingletonLevelState alloc] init];
sharedLevelStateInstance->gameRuns = 1;
sharedLevelStateInstance->totalScore = 0;
}
}
return sharedLevelStateInstance;
}
This is working great as I can reference this class from any other class and always get a pointer to the same object, so this works fine from other objects:
sharedLevelState = [SingletonLevelState sharedLevelStateInstance];
sharedLevelStateInstance.gameRuns++;
Now I added the NSCoder protocol, and added the two methods initWithCoder and encodeWithCoder as follows :
- (void) encodeWithCoder: (NSCoder *)coder
{
//encode level data
[coder encodeInt:self->gameRuns forKey:#"gameRuns"];
[coder encodeInt:self->totalScore forKey:#"totalScore"];
}
- (id) initWithCoder: (NSCoder *) coder
{
if(self = [super init]){
self->gameRuns = [coder decodeIntForKey:#"gameRuns"];
self->totalScore = [coder decodeIntForKey:#"totalScore"];
}
return self;
}
Now when the app loads, I check to see if we already have a saved sate, if it exists, I just unarchive the class with that file, if not, I init that class using my custom method above, then set its defaults, encode it to file so we have a saved state, here's the code:
//Load Level state
sharedLevelStateInstance = [SingletonLevelState sharedLevelStateInstance];
//Check if file is saved
NSFileManager *fm = [[NSFileManager alloc] init];
NSString *gameStatePath = [NSString stringWithString:[self getSavePath]];
if([fm fileExistsAtPath:gameStatePath]){
[self loadState];
sharedLevelStateInstance.gameRuns = sharedLevelStateInstance.gameRuns+1;
NSLog(#"Loaded %d times", [sharedLevelStateInstance gameRuns]);
}
[fm release];
Now the last line in the if statement works perfectly, it increments every time I load the app as expected and I feel really happy lol.
However, the problem arises when I try to get a reference of the singleton in another class by doing the following:
sharedLevelStateInstance = [SingletonLevelState sharedLevelStateInstance];
NSLog(#"Played: %d times", sharedLevelStateInstance.gameRuns);
It always counts back to 1, I know what happens but I'm not sue what's the best way to solve it, when I initWithCoder the singleton, It's not returning a static object, it creates a new one, when I init my sharedLevelStateInstance, it calls my first custom method, initializing it to the defaults hardcoded.
So StackOverflow, can you please help me ?!
I just need to know what's the best way to get a reference to the same object without allocating a new one every time I initWithCoder !
Thanks :)
So, you code should probably look like this:
if(self = [[SingletonLevelState sharedLevelStateInstance] retain])
Which sets the variables of the singleton, and returns the singleton. Be sure to retain the singleton, so that when the NSCoder releases this instance, it doesn't fully deallocate your singleton.

Custom Classes not being retained

I have a NSXMLParser that parses YT's API. It then turns all the videos into a class I wrote called video. Then the class is put into an array. Then back at my rootviewcontroller I access xmlParser.allVideos (xmlParser is a class I wrote that is the delegate for the xml parser.
Here is what I do with it in the viewDidLoad:
arrayFromXML = xmlParser.allVideos;
Then in the drawing of the tableView I do this:
tempVideo = [arrayFromXML objectAtIndex:indexPath.row];
cell.textLabel.text = tempVideo.videoTitle; //crashes here and says NSString has been deallocated.
How can I fix this?
If arrayFromXML is an instance variable you have to retain or copy (to be safe from later manipulation) the array as xmlParser simply might not be alive anymore when other methods are called later:
arrayFromXML = [xmlParser.allVideos copy];
Or better yet using a copy or retain property:
self.arrayFromXML = xmlParser.allVideos;

How to duplicate a UIButton in Objective C?

The object inherits from NSObject.
Is there a method to create a copy of it as a new object?
UIButton does not conform to NSCopying, so you cannot make a copy via -copy.
However, it does conform to NSCoding, so you can archive the current instance, then unarchive a 'copy'.
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject: button];
UIButton *buttonCopy = [NSKeyedUnarchiver unarchiveObjectWithData: archivedData];
Afterwards, you'll have to assign any additional properties that weren't carried over in the archive (e.g. the delegate) as necessary.
UIButton doesn't conform to the NSCopying protocol, so you have copy it by hand. On the other hand, it is not a bad thing, since it is not exactly clear what does it mean to copy a button. For example, should it add the button copy to the same view the original is in? Should it fire the same methods when tapped?
To add to Jim's answer above using a category
#implementation UIButton (NSCopying)
- (id)copyWithZone:(NSZone *)zone {
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:self];
UIButton *buttonCopy = [NSKeyedUnarchiver unarchiveObjectWithData: archivedData];
return buttonCopy;
}
#end
if you wanted to copy all of the actions from one button to another, add something like this:
for (id target in button.allTargets) {
NSArray *actions = [button actionsForTarget:target forControlEvent:UIControlEventTouchUpInside];
for (NSString *action in actions) {
[newButton addTarget:target action:NSSelectorFromString(action) forControlEvents:UIControlEventTouchUpInside];
}
}
If it implements the NSCopying protocol, then the -copy method should do the trick.
You can get more information about the -copy method and how it works with sub-objects on the ADC reference site. As Stephen Darlington mentions, you need to implement the NSCopying protocol in your object.
documentation
Swift 3/4 version would be:
let archivedData = NSKeyedArchiver.archivedData(withRootObject: button as Any)
let buttonCopy = NSKeyedUnarchiver.unarchiveObject(with: archivedData) as? UIButton