I have an app with array of objects, which i archived and unarchived
-(id)initWithCoder:(NSCoder *)aDecoder{
title = [aDecoder decodeObjectForKey:#"Title"];
image = [aDecoder decodeObjectForKey:#"Image"];
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:title forKey:#"Title"];
[aCoder encodeObject:image forKey:#"Image"];
}
will UIImage store okay in this way?
No, UIImage doesn't conform to the NSCoding protocol.
To save an image, convert it to NSData using UIImageJPEGRepresentation(image, quality) or UIImagePNGRepresentation(image) and then you can save the NSData object in your coder because it does conform to NSCoding.
Like this:
-(id)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super init])){
title = [aDecoder decodeObjectForKey:#"Title"];
image = [UIImage imageWithData:[aDecoder decodeObjectForKey:#"ImageData"]];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:title forKey:#"Title"];
[aCoder encodeObject:UIImagePNGRepresentation(image) forKey:#"ImageData"];
}
PS, I assume you're using ARC? If you aren't you'll need to retain the values in your initWithCoder method because decodeObjectForKey: returns an autoreleased object. I've also rewritten your initWithCoder to include the normal super/nil check, which is best practice.
Note that you may wish to use self = [self init] or self = [super initWithCoder:aDecoder] instead of self = [super init], depending on what your superclass is and whether your init does any additional setup.
Coder and Decoder , which was implement in the question are OK
Related
I am trying to save an array containing models which consist of an image and a string using [NSKeyedArchiver archivedDataWithRootObject:dataArray]; but this method returns nil after 2-3 times of calling this method .
This only happens when the model I am trying to archive contains image.
I have checked that data array is not nil.
Beneath is code I am using :
#implementation ImagesModel
- (id)initWithCoder:(NSCoder *)coder{
if(self = [super init]){
self.image = [[UIImage alloc]initWithData:[coder decodeObjectForKey:#"image"]];
self.urlString = [coder decodeObjectForKey:#"urlString"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:UIImagePNGRepresentation(self.image) forKey:#"image"];
[coder encodeObject:self.urlString forKey:#"urlString"];
}
+ (NSDictionary *)getObjectMapper {
if (!mapperDictionary) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setObject:[PropertyMapping mappingForSimpleTypeWithMappedKeyName:#"image"] forKey:#"image"];
[dictionary setObject:[PropertyMapping mappingForSimpleTypeWithMappedKeyName:#"urlString"] forKey:#"urlString"];
mapperDictionary = [dictionary copy];
}
return mapperDictionary;
}
- (NSData)ArchiveData {
if (!dataArray){
return NO; //nothing to save
}
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dataArray]; //Return Nil
}
It depends on how you have loaded your image.
Here is an answer that might help you:
How to encode a UIImage in an `NSCoder`
Also, you might want to use UIImageJPEGRepresentation if you want to store .JPEG images.
I have a custom object, LevelContent, which contains some properties. LevelContentconforms to NSCoding, and I have implemented encodeWithCoder: and initWithCoder: methods. I save and fetch the data to Parse.com. Saving works fine like this:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.currentLevelContent];
When I fetch the data, I get the NSDatacorrectly, but when I try to initialize the LevelContentwith the downloaded data, initWithCoder: never gets called. I try to load the LevelContent with this:
LevelContent *content = [NSKeyedUnarchiver unarchiveObjectWithData:data];
Here is the code for encoding/decoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.tiles forKey:#"tiles"];
[aCoder encodeObject:self.attributes forKey:#"attributes"];
[aCoder encodeObject:self.level forKey:#"level"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self == [super init]) {
self.tiles = [NSMutableArray array];
[self.tiles addObjectsFromArray:[aDecoder decodeObjectForKey:#"tiles"]];
self.attributes = [NSMutableDictionary dictionary];
[self.attributes addEntriesFromDictionary:[aDecoder decodeObjectForKey:#"attributes"]];
self.level = [Level levelWithTopIndex:0 detailIndex:0];
self.level = [aDecoder decodeObjectForKey:#"level"];
}
return self;
}
Change if (self == [super init]) to if (self = [super init]) in initWithCoder:
Try this,
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
.....
}
return self;
}
So here is my problem. I am trying to archive an array of objects and when I unarchive them although the count is the same and the objects inside the root object are the same it does not find the specified object in the unarchived array.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"self.vendors.count = %d", self.vendors.count);
[NSKeyedArchiver archiveRootObject:self.vendors toFile:[self vendorsArchivePath]];
NSMutableArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:[self
vendorsArchivePath]];
NSLog(#"array.count = %d", array.count);
NSLog(#"%d", [self.vendors indexOfObject:self.vendors[indexPath.row]]);
NSLog(#"%d", [array indexOfObject:self.vendors[indexPath.row]]);
}
In order to troubleshoot myself; I have the counts of both arrays listed and at the bottom and the index of the object in each array in an NSLog.
Here is the log...
2014-04-27 12:53:04.813 Inventory[12272:907] self.vendors.count = 5
2014-04-27 12:53:04.827 Inventory[12272:907] array.count = 5
2014-04-27 12:53:04.828 Inventory[12272:907] 0
2014-04-27 12:53:04.831 Inventory[12272:907] 2147483647
So my question is: Why in the world is the last NSLog not finding the same object, that I archived and unarchived in the SAME function not being found in the array? Are objects modified when archived and unarchived? Or is there something else that I am missing.
EDIT: Here are my vendor.h and vendor.m files:
//vendor.h
#import <Foundation/Foundation.h>
#interface SELVendor : NSObject <NSCoding>
#property (nonatomic) NSMutableArray *itemsAvailable;
#property (nonatomic) NSString *name;
#property (nonatomic) NSString *phoneNumber;
#property (nonatomic) NSString *email;
#property (nonatomic) NSString *vendorKey;
#end
//vendor.m
#import "SELVendor.h"
#implementation SELVendor
-(instancetype) init {
self = [super init];
if (self) {
self.name = #"Unnamed Vendor";
self.itemsAvailable = [[NSMutableArray alloc] init];
NSUUID *uuid = [[NSUUID alloc] init];
self.vendorKey = [uuid UUIDString];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.name forKey:#"name"];
[aCoder encodeObject:self.itemsAvailable forKey:#"itemsAvailable"];
[aCoder encodeObject:self.phoneNumber forKey:#"phoneNumber"];
[aCoder encodeObject:self.email forKey:#"email"];
[aCoder encodeObject:self.vendorKey forKey:#"vendorKey"];
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.name = [aDecoder decodeObjectForKey:#"name"];
self.itemsAvailable = [aDecoder decodeObjectForKey:#"itemsAvailable"];
self.phoneNumber = [aDecoder decodeObjectForKey:#"phoneNumber"];
self.email = [aDecoder decodeObjectForKey:#"email"];
self.vendorKey = [aDecoder decodeObjectForKey:#"vendorKey"];
}
return self;
}
#end
This means that the object wasn't found 2147483647 == NSNotFound
What you're trying to do is save a array of objects, and then read them back out to a varaible. Doing this will create (alloc/init) the objects again so they won't occupy the same location in memory. Depending on how you implement your encoder/decode methods, the object might be exactly the same as the other one (content wise). What you need to do is override isEqual: (and hash) in your class and check the objects in there.
If you're objects have some kind of ID property you can just do:
-(BOOL)isEqual:(id)object
{
if (self == object) {
return YES;
}
if (![object isKindOfClass:[self class]]) {
return NO;
}
return [self.ID isEqual:object.ID];
}
If none of your properties on your object are unique, you can check each one in the isEqual: method.
See this answer for more info.
indexOfObject: uses isEqual: to test whether an object in the array is the "same" as the object you're looking for. When you archive and unarchive an object, the resulting object is not at the same memory address (it is effectively a copy of the original object).
Since the default implementation of isEqual: return YES only for the same object (memory address), this explains the behavior you're seeing. You probably want to override isEqual: (and hash, which you should usually override if you override isEqual:) to check whatever member variables are important for your notion of equality. (Or you can use one of a zillion libraries out there that make this easier, such as Mantle.)
So I know my problem is that my transfer array is not being initialized correctly. Where should I put transfer = [[NSMutableArray alloc] init];?
#interface PictureViewController (){
Poi *labelPoi;
}
#end
#implementation PictureViewController
#synthesize imageX;
#synthesize imageY;
#synthesize name;
#synthesize transfer;
- (id)init
{
self = [super init];
if(self) {
transfer = [[NSMutableArray alloc] init];
}
return self;
}
-(id)initWithLabel:(double)imageX andY:(double)imageY withName:(NSString *)name{
self = [super init];
if(self){
// transfer = [[NSMutableArray alloc] init];
self.imageX = imageX;
self.imageY = imageY;
self.name = name;
}
return self;
}
/*-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if(self){
transfer = [[NSMutableArray alloc] init];
self.imageX = imageX;
self.imageY = imageY;
self.name = name;
NSLog(#"imageX: %f", self.imageX);
NSLog(#"imageY: %f", imageY);
NSLog(#"name: %#", name);
}
return self;
}*/
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSLog(#"transfer count: %lu",(unsigned long)transfer.count);
for(int i = 0; i < transfer.count; i++){
UILabel *label = [[UILabel alloc] initWithFrame: CGRectMake([[transfer objectAtIndex:i] imageLocationX], [[transfer objectAtIndex:i] imageLocationY], 200, 50)];
label.text = [[transfer objectAtIndex:i] name];
label.font = [UIFont systemFontOfSize:14];
label.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.0];
[self.view addSubview:label];
NSLog(#"asdfasdsd");
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (id)display:(double)imageXX andY:(double)imageYY withName:(NSString *)namee{
NSLog(#"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
NSLog(#"imageX: %f",imageXX);
NSLog(#"imageY: %f", imageYY);
NSLog(#"name: %#", namee);
labelPoi = [[Poi alloc] init];
//transfer = [[NSMutableArray alloc] init];
labelPoi.imageLocationX = imageXX;
labelPoi.imageLocationY = imageYY;
labelPoi.name = namee;
[transfer addObject:labelPoi];
NSLog(#"label.x: %f should be: %f", labelPoi.imageLocationX, imageXX);
NSLog(#"label.y: %f should be: %f", labelPoi.imageLocationY, imageYY);
NSLog(#"label.name: %# should be: %#",labelPoi.name,namee);
NSLog(#"transssssfer: %lu", (unsigned long)transfer.count);
NSLog(#"asfddfsaasfdfdsfsd %f", [[transfer objectAtIndex:0] imageLocationX]);
return self;
}
#end
The Poi object is made up of an imageLocationX, imageLocationY, and name and I am trying to put the Poi object into an array named transfer however, whenever I try to access transfer elements, I receive 0 or null. The (id)display function is being called several times from a different view NSMutable alloc in that function, the array gets reset.
Here is the output:
2013-07-19 11:22:06.736 AR_UAV_App[12466:11303] %%%%%%%%%%%%%%%%%%%%%%%%
2013-07-19 11:22:06.736 AR_UAV_App[12466:11303] imageX: 224.485718
2013-07-19 11:22:06.736 AR_UAV_App[12466:11303] imageY: 116.353401
2013-07-19 11:22:06.736 AR_UAV_App[12466:11303] name: Beutel Student Health Center
2013-07-19 11:22:06.736 AR_UAV_App[12466:11303] label.x: 224.485718 should be: 224.485718
2013-07-19 11:22:06.736 AR_UAV_App[12466:11303] label.y: 116.353401 should be: 116.353401
2013-07-19 11:22:06.736 AR_UAV_App[12466:11303] label.name: Beutel Student Health Center should be: Beutel Student Health Center
2013-07-19 11:22:06.737 AR_UAV_App[12466:11303] transssssfer: 0
2013-07-19 11:22:06.737 AR_UAV_App[12466:11303] asfddfsaasfdfdsfsd 0.000000
2013-07-19 11:22:06.737 AR_UAV_App[12466:11303] #############################################################
EDIT: .h file
#interface PictureViewController : UIViewController{
NSMutableArray *transfer;
}
#property (nonatomic) double imageX;
#property (nonatomic) double imageY;
#property (nonatomic) NSString *name;
#property (nonatomic,retain) NSArray *transfer;
- (IBAction)backView:(id)sender;
- (IBAction)load:(id)sender X:(double)imageX andY:(double)imageY withName:(NSString *)name;
-(id)initWithLabel:(double)imageX andY:(double)imageY withName:(NSString *)name;
-(id)display:(double)imageX andY:(double)imageY withName:(NSString *)name;
#end
This init method should become the "designated" initializer:
-(id)initWithLabel:andY:withName:(NSString *)name
(by the way, it's not named correctly according the naming conventions)
The designated initializer shall initialize the instance properly (that is, in your case it may initialize the array transfer, unless you use a lazy accessor ).
The "designated initializer" is most often the "most specialized" initializer - that is, that one with the most parameters. Most often, there is only one and easily identifiable designated initializer.
The "designated initializer" has the canonical form:
-(id)initWithLabel:(double)imageX andY:(double)imageY withName:(NSString *)name{
self = [super init];
if(self){
// initialization
...
}
}
Other init methods like the init method shall invoke the designated initializer like:
- (id)init {
return [self initWithLabel:0 andY:0 withName:#""];
}
I guess one problem can be that when you are creating this controller you are calling to:
-(id)initWithLabel:(double)imageX andY:(double)imageY withName:(NSString *)name
In case you do that, you are calling to [super init] what will never go through the init method of your class (where you allocated the array). In case you have different methods to init a controller I recommend you to have a:
commonInit{
_transfer = [[NSMutableArray] alloc] init];
}
Then call this method for every init method that you have in your controller so you ensure that array is allocated.
Other thing is just allocate your array in the viewDidLoad of your controller.
Just for making know, you can "allocated" an array without having to take care about release the object by calling to [NSMutableArray arrayWithCapacity:0];
Are you initializing your PictureViewController with -(id)initWithLabel:(double)imageX andY:(double)imageY withName:(NSString *)name? If so, I can tell you what the problem is...
-(id)initWithLabel:(double)imageX andY:(double)imageY withName:(NSString *)name
{
self = [super init]; // <--- You are likely confused about this line
.....
}
[super init] does not pass a message to your custom -(id)init method. It instead is referring to PictureViewController's superclass, which is likely UIViewController.
Your problem should be fixed by uncommenting transfer = [[NSMutableArray alloc] init]; in the initWithLabel: custom init method.
make a property (synthesize it #synthesize transfer=_transfer;), use lazy instantiation like:
-(NSMutableArray*)transfer
{
if (!_transfer)
{
_transfer=[NSMutableArray array];
}
return _transfer;
}
and take it out of your init
I think I am hallucinating. I am trying to add some persistence to my Concentration-lke game. I would like to keep track of high scores. I got this partially working for a little while today and now it has all gone kablooie (I think that is the correct iOS terminology). Now, my allHighScores NSMutablearray suddenly becomes a CALayer. I am using NSKeyed Archiving. I have a break point in my file before allHighScores gets loaded with data. When stepping through the application, allHighScores exists as an NSMutableArray - then, at the next step, it suddenly becomes a CA Layer. Huh?
-(id)init
{
self = [super init];
if (self) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
allHighScores = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if (!allHighScores) {
allHighScores = [[NSMutableArray alloc] init];
}
}
return self;
}
+(FlipHighScoreStore *)sharedStore {
static FlipHighScoreStore *sharedStore = nil;
if (!sharedStore) {
sharedStore = [[super allocWithZone:nil]init];
}
return sharedStore;
}
Somehow, calling NSKeyedUnarchiver changes my allHighScores from an NSMutableArray into a CALayer. I am very confused.
I tried adding a retain to the unarchiving instruction, but that didn't help.
Here is my encoding/decoding code:
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.themeChosen forKey:#"themeChosen"];
[aCoder encodeInt:self.highScore forKey:#"highScore"];
[aCoder encodeInt:self.scoreStartLevel forKey:#"scoreStartLevel"];
[aCoder encodeInt:self.scoreFinishLevel forKey:#"scoreFinishLevel"];
[aCoder encodeObject:scoreDateCreated forKey:#"scoreDateCreated"];}
-(id)initWithCoder:(NSCoder *)aDecoder {
if (self) {
self.themeChosen = [aDecoder decodeObjectForKey:#"themeChosen"];
self.highScore = [aDecoder decodeIntForKey:#"highScore"];
self.scoreStartLevel = [aDecoder decodeIntForKey:#"scoreStartLevel"];
self.scoreFinishLevel = [aDecoder decodeIntForKey:#"scoreFinishLevel"];
scoreDateCreated = [aDecoder decodeObjectForKey:#"scoreDateCreated"];
}
return self;}
UPDATE: The program crashes when a "highscores.archive" file already exists and a save is called again. I can launch the app, look at the high scores - they are there and retrieved happily, but the save code:
-(BOOL)saveHighScores {
NSString *path = [self flipScoreArchivePath];
return [NSKeyedArchiver archiveRootObject:allHighScores toFile:path];}
causes a EXC_BAD_ACCESS. The path is right, so somehow the allHighScores isn't.
The problem here is you aren't retaining the results of the unarchiving. According to the Basic Memory Management Rules, a method by the name of +unarchiveObjectWithFile: will return an autoreleased object. As such, since you are placing it into an ivar, you need to retain this object, or it will get deallocated out from under you.
Although in your case, since you want a mutable array, you actually need to call -mutableCopy since NSKeyedUnarchive will just give you an immutable array.
-(id)init {
if ((self = [super init])) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
allHighScores = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] mutableCopy];
if (!allHighScores) {
allHighScores = [[NSMutableArray alloc] init];
}
}
return self;
}
Your -initWithCoder: isn't calling super. You need to say
if ((self = [super initWithCoder:aDecoder])) {
Have you tried this?
-(id)init {
if ((self = [super init])) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
allHighScores = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] mutableCopy];
if (!allHighScores) {
allHighScores = [[NSMutableArray alloc] init];
}
// All-important new line....
[self setAllHighScores:allHighScores];
}
return self;
}
Edit/Update:
So, here's two versions of what I actually intended in the above example (I'm assuming here that his ivar allHighScores has a corresponding property):
-(id)init {
if ((self = [super init])) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
self.allHighScores = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] mutableCopy];
if (!self.allHighScores) {
self.allHighScores = [[NSMutableArray alloc] init];
}
}
return self;
}
This is the way I'd actually do it:
-(id)init {
if ((self = [super init])) {
NSMutableArray *arr = [[NSKeyedUnarchiver unarchiveObjectWithFile:[self flipScoreArchivePath]] mutableCopy];
if (!arr) arr = [[NSMutableArray alloc] init];
[self setAllHighScores:arr];
}
return self;
}