I have a sticker class and I am saving sticker objects to a NSMutableDictionary. Sticker class below:
#import "Sticker.h"
#implementation Sticker
-(instancetype)initWithTitle:(NSString *)title stickerNO:(int)stickerNO image:(UIImage *)image {
self=[super init];
if(self){
self.title=title;
self.stickerNO=[NSNumber numberWithInt:stickerNO];;
self.image=image;
}
return self;
}
//CODING METHODS//////////////
-(void)encodeWithCoder:(NSCoder *)aCoder{
//choose what we save, these are objects
[aCoder encodeObject:self.title forKey:#"title"];
[aCoder encodeObject:self.stickerNO forKey:#"stickerNO"];
[aCoder encodeObject:self.image forKey:#"image"];
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
self=[super init];
if(self){
self.title=[aDecoder decodeObjectForKey:#"title"];
self.stickerNO=[aDecoder decodeObjectForKey:#"stickerNO"];
self.image=[aDecoder decodeObjectForKey:#"image"];
}
return self;
}
#end
The dictionary is managed by the class StickerManager:
#implementation StickerManager
-(instancetype)init {
self = [super init];
//load the dictionary
NSString *path = [self itemArchivePath];
self.stickerDictionary=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
//if there is no dictionary create it and add the default
if(!self.stickerDictionary) {
NSLog(#"Creating dictionary and adding default");
self.stickerDictionary=[[NSMutableDictionary alloc] init];
[self addDefaultStickers];
[self saveStickerDictionary];
}
//if empty fill it
else if ([self.stickerDictionary count]==0){
NSLog(#"Dictionary exists but it empty");
[self addDefaultStickers];
[self saveStickerDictionary];
}
return self;
}
//add the stickers included in the app bundle
-(void)addDefaultStickers {
Sticker *sticker = [[Sticker alloc] initWithTitle:#“Batman” stickerNO:1 image:[UIImage imageNamed:#“batman.png"]];
[self.stickerDictionary setObject:sticker forKey:sticker.title];
}
-(BOOL)saveStickerDictionary{
NSLog(#"Saving stickers");
//get the path from above
NSString *path = [self itemArchivePath];
return [NSKeyedArchiver archiveRootObject:self.stickerDictionary toFile:path];
}
-(NSString *)itemArchivePath
{
//get the directory
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//set it to a string
NSString *documentDirectory = [documentDirectories firstObject];
//here we call the file items.archive
return [documentDirectory stringByAppendingPathComponent:#"stickers.archive"];
}
#end
If when the StickerManager is init if it is empty or doesn't exist it will create and fill the dictionary with the default stickers by calling addDefaultStickers. This is working for the one sticker I have in the code. I can load and restore the dictionary and use NSLog to check the contents. The sticker is there but for some reason the UIImage is null and I can't display it. I'm really not sure why, I have used the encodeWithCoder for it so shouldn't it work? The odd thing is that if I download a sticker from Parse.com (I have an identical class on it) and then convert that image from NSData to png and save it will work. Could someone give me some pointers please to what might be going wrong here? Thanks
EDIT This is my download from Parse.com code:
- (void)getNewStickersWithCompletionHandler:(stickerCompletionHandler)handler
{
__weak StickerManager *weakSelf = self;
PFQuery *stickersQuery = [PFQuery queryWithClassName:#"sticker"];
[stickersQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(!error) {
for( PFObject *object in objects) {
NSString *title = object[#"title"];
int stickerNO = [[object objectForKey:#"stickerNO"] intValue];
//DOWNLOAD IMAGE CODE
PFFile *image = object[#"image"];
[image getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if(!error){
UIImage *image = [UIImage imageWithData:data];
Sticker *sticker = [[Sticker alloc] initWithTitle:title stickerNO:stickerNO image:image];
[weakSelf.stickerDictionary setObject:sticker forKey:sticker.title];
[self saveStickerDictionary];
handler(YES,nil);
}
}];//end get image block
}//end for
}
}];//end download stickers block
}
If I save a sticker to the dictionary this way there are no issues and I can display the image. This is almost identical to the addDefaultStickers, I am saving a UIImage and not saving the NSData. No Idea what is up here..
just for other people's reference the problem was in not first converting the UIImage to NSData. I changed UIImage lines in the aCoder method to below:
[aCoder encodeObject:UIImagePNGRepresentation(self.image) forKey:#"image"];
and in the aDecoder: self.image = [UIImage imageWithData:[aDecoder decodeObjectForKey:#"image"]];
don't totally understand why it was working for NSData that was converted to a UIImage for the Parse download before but it's all working now anyway.
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 can't find anything online about threading loading an image from a device and scrolling smoothly through a tableview. There is one on ray wen about this, but it doesn't really help me for my situation.
Does anybody have any advice or code which would help to allow a tableview to scroll smoothly and load images from the device's temporary directory?
i did exactly as mentioned at tutorial, but with modification for nsoperation subclass
this is methods for fetch
-(void) updateData
{
[self.pendingOperations.downloadQueue addOperationWithBlock:^{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSArray *filePathes = [self recursiveRecordsForResourcesOfType:#[#"png", #"jpeg", #"jpg",#"pdf"] inDirectory:documentsDirectory];
#synchronized (self) {
self.documents = filePathes;
NSLog(#"documents count %#", #([self.documents count]));
}
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
[self.delegate modelDidUpdate:self];
});
}];
}
- (NSArray *)recursiveRecordsForResourcesOfType:(NSArray *)types inDirectory:(NSString *)directoryPath{
NSMutableArray *filePaths = [[NSMutableArray alloc] init];
NSMutableDictionary *typesDic = [NSMutableDictionary dictionary];
for (NSString *type in types)
[typesDic setObject:type forKey:type];
// Enumerators are recursive
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:directoryPath];
NSString *filePath;
while ((filePath = [enumerator nextObject]) != nil){
// If we have the right type of file, add it to the list
// Make sure to prepend the directory path
if([typesDic objectForKey:[filePath pathExtension]]){
//[filePaths addObject:[directoryPath stringByAppendingPathComponent:filePath]];
CURFileRecord *record = [CURFileRecord new];
record.filePath =[directoryPath stringByAppendingPathComponent:filePath];
record.fileName = filePath;
[filePaths addObject:record];
}
}
return filePaths;
}
this is .m for subclass
- (void)main {
// 4
#autoreleasepool {
if (self.isCancelled)
return;
NSData *fileData = [[NSFileManager defaultManager] contentsAtPath:self.fileRecord.filePath];
// self.fileRecord.fileData = fileData;
if (self.isCancelled) {
fileData = nil;
return;
}
if (fileData) {
UIImage *newImage;
if ([[self.fileRecord.filePath pathExtension] isEqualToString:#"pdf"])
{
CGPDFDocumentRef doc = [CURDocumentViewerUtilities MyGetPDFDocumentRef:fileData];
newImage = [CURDocumentViewerUtilities buildThumbnailImage:doc withSize:CGSizeMake(64, 96)];
}
else
{
newImage = [CURDocumentViewerUtilities makePreviewImageFromData:fileData];
}
self.fileRecord.previewImage = newImage;
}
else {
self.fileRecord.failed = YES;
}
fileData = nil;
if (self.isCancelled)
return;
// 5
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(imageDownloaderDidFinish:) withObject:self waitUntilDone:NO];
}
}
With update func i've fetched pathes to proccess, and nsoperation subclass loads images. Works fine with 2000 images in fullhd - smoothly and without any lugs
I have an array of Objects (containing an item name and a creation date) used to display table data. I want to save this array using NSCoding protocol. I call my saveDataToDisk method every time a "submit" button is pressed to submit a new table entry. However, my app crashes upon hitting submit. This is my first time learning how to save app data and I looked a several tutorials and my form seems to be the same. I also looked for several days on the internet and couldn't find a solution. What is wrong?
Here are my methods:
Decoder Method:
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (!self) {
return nil;
}
self.itemArray = [decoder decodeObjectForKey:#"itemArray"];
return self;
}
Encoder Method:
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.itemArray forKey:#"itemArray"];
}
Path Finder Method:
- (NSString*) pathForDataFile
{
NSFileManager * fileManager = [NSFileManager defaultManager];
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *filePath = [documentsPath stringByAppendingPathComponent:#"appData"];
if([fileManager fileExistsAtPath:filePath] == NO)
{
[fileManager createDirectoryAtPath:filePath withIntermediateDirectories:NO attributes:nil error:nil];
}
NSString *fileName = #"Budgetlist.appData";
return [filePath stringByAppendingPathComponent: fileName];
}
Save Data Method:
- (void) saveDataToDisk
{
NSString * path = [self pathForDataFile];
NSMutableDictionary * rootObject;
rootObject = [NSMutableDictionary dictionary];
[rootObject setValue: [self itemArray] forKey:#"itemArray"];
[NSKeyedArchiver archiveRootObject: rootObject toFile: path];
}
Load Data Method:
- (void) loadDataFromDisk
{
NSString * path = [self pathForDataFile];
NSDictionary * rootObject;
rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
[self setItemArray: [rootObject valueForKey:#"itemArray"]];
}
My Button:
- (IBAction)submitButton:(id)sender {
if (self.textField.text.length > 0 && [self.textField.text floatValue]>0) {
self.item = [[Data alloc] init];
self.item.itemName = self.textField.text;
[self.itemArray addObject:self.item];
[self.tableView reloadData];
[self saveDataToDisk];
}
}
You are trying to save an NSArray with custom objects.
Your objects must implement NSCoding protocol as well.
Implement those:
- (id)initWithCoder:(NSCoder *)decoder
- (void)encodeWithCoder:(NSCoder *)encoder
in your "Data" class.
NSArray is going through all objects and call those methods for each object inisde an array
I am developing one app in that getting images from array and display vertically in ScrollView.
when user double tapped on particular image i want that exact image store into plist according to tag value of that image, and retrieve that image later on when require.
i tried this one
// Store Data into plist.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES);
NSString *path = [NSString stringWithFormat:#"%#/myPlist.plist",
[paths objectAtIndex:0]];
// Place an image in a dictionary that will be stored as a plist
NSMutableDictionary * dictionary=[[NSMutableDictionary alloc]init];
[dictionary setObject:ImgView.tag forKey:#"image"];
NSLog(#"%#",dictionary);
// Write the dictionary to the filesystem as a plist
[NSKeyedArchiver archiveRootObject:dictionary toFile:path];
// For getting data from NSmutable array store it to the scrollview.
int m=0;
AppDelegate * delegate=(AppDelegate *)[[UIApplication sharedApplication]delegate];
delegate.front=TRUE;
delegate.back=FALSE;
UIScrollView *scrollView=[[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, 320, 480)];
[scrollView setPagingEnabled:YES];
[scrollView setShowsHorizontalScrollIndicator:NO];
FrontsCards=[[NSMutableArray alloc]initWithObjects:#"cloub1.png",#"cloub2.png",#"cloub3.png",#"cloub4.png",#"cloub5.png",#"cloub6.png",#"cloub7.png",#"cloub8.png",#"cloub9.png",#"cloub10.png",#"cloub11.png",#"cloub12.png",#"diamond1.png",#"diamond2.png",#"diamond3.png",#"diamond4.png",#"diamond5.png", nil];
for(m=0; m<[FrontsCards count];m++)
{
ImgView.alpha=1;
ImgView.tag=m;
int randIdx=arc4random()%[FrontsCards count];
NSString *imageName=[FrontsCards objectAtIndex:randIdx];
NSString *fullImageName=[NSString stringWithFormat:#"%#",imageName];
int padding=0;
CGRect imageViewFrame=CGRectMake(scrollView.frame.size.width*m+padding, scrollView.frame.origin.y, scrollView.frame.size.width-2*padding, scrollView.frame.size.height);
ImgView=[[UIImageView alloc]initWithFrame:imageViewFrame];
[ImgView setImage:[UIImage imageNamed:fullImageName]];
NSLog(#"%d",m);
// Place an image in a dictionary that will be stored as a plist
//[dictionary setObject:image forKey:#"image"];
// Write the dictionary to the filesystem as a plist
//[NSKeyedArchiver archiveRootObject:dictionary toFile:path];
[scrollView addSubview:ImgView];
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleTapImgView:)];
doubleTap.numberOfTapsRequired = 2;
doubleTap.delegate = self;
[self.ImgView addGestureRecognizer:doubleTap];
self.ImgView.userInteractionEnabled=YES;
}
CGSize scrollViewSize=CGSizeMake(scrollView.frame.size.width*[FrontsCards count], scrollView.frame.size.height);
[scrollView setContentSize:scrollViewSize];
[self.view addSubview:scrollView];
help me out this thanks in advance.
Define this MACRO Definition at the top of your .m file
#define LIB_DIR_PATH NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0]
Use this function to Save Image to Plist with Image and Name
- (void)saveImage:(UIImage *)image WithName:(NSString *)imageName
{
// If File Exist then read it otherwise creat new
NSMutableDictionary *imageInfoDict;
if([[NSFileManager defaultManager] fileExistsAtPath:[LIB_DIR_PATH stringByAppendingPathComponent:#"imageInfo.plist"]])
{
NSData *fileData = [NSData dataWithContentsOfFile:[LIB_DIR_PATH stringByAppendingPathComponent:#"imageInfo.plist"]];
imageInfoDict = [NSMutableDictionary dictionaryWithDictionary:[NSKeyedUnarchiver unarchiveObjectWithData:fileData]];
}
else
imageInfoDict = [NSMutableDictionary dictionaryWithCapacity:0];
// Add Single Image to Dictionary
[imageInfoDict setValue:image forKey:imageName];
// Convert Main info Dictionary to `NSData` to Save on Disc
[NSKeyedArchiver archiveRootObject:imageInfoDict toFile:[LIB_DIR_PATH stringByAppendingPathComponent:#"imageInfo.plist"]];
// To Read Stored Image Use Following Code
[self readImageFromPlistByKey:imageName];
}
This function returns image for respective name from Plist
-(UIImage *)readImageFromPlistByKey:(NSString *)keyName
{
// If File Exist then read it otherwise creat new
NSMutableDictionary *imageInfoDict;
if([[NSFileManager defaultManager] fileExistsAtPath:[LIB_DIR_PATH stringByAppendingPathComponent:#"imageInfo.plist"]])
{
NSData *fileData = [NSData dataWithContentsOfFile:[LIB_DIR_PATH stringByAppendingPathComponent:#"imageInfo.plist"]];
if([fileData length] > 0)
{
// Read Plist
imageInfoDict = [NSMutableDictionary dictionaryWithDictionary:[NSKeyedUnarchiver unarchiveObjectWithData:fileData]];
// Here is your Image
return imageInfoDict[keyName];
}
}
else
{
// Return Default Image if not Found
return [UIImage imageNamed:#"Default.png"];
}
}
If you are going to just store indexes, you need to have a master imageArray. I added insert/delete when user double taps the imageView twice.
- (void)doubleTapImgView:(UITapGestureRecognizer *)recognizer
{
UIImageView *imageView = (UIImageView *)recognizer.view;
[self insertorDeleteImageIndex:imageView.tag-1];
}
- (NSString *)plistFilePath{
NSString *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
return [documents stringByAppendingPathComponent:#"ImageIndexes.plist"];
}
- (void)insertorDeleteImageIndex:(NSInteger)index{
NSString *filePath = [self plistFilePath];
NSMutableArray *savedIndexes = [NSMutableArray arrayWithContentsOfFile:filePath];
if (!savedIndexes) {
savedIndexes = [NSMutableArray array];
}
if (![savedIndexes containsObject:#(index)]) {
[savedIndexes addObject:#(index)];
}else{
[savedIndexes removeObject:#(index)];
}
[savedIndexes writeToFile:filePath atomically:YES];
}
- (NSArray *)savedImageIndexes{
NSString *filePath = [self plistFilePath];
return [NSArray arrayWithContentsOfFile:filePath];
}
Source code
The code you post above can't be the real code as it wouldn't compile. That said, it shows a few errors:
You can't put basic numbers (NSInteger) into a dictionary, it needs to be boxed in an NSNumber.
You're setting the tag of the image before you create the instance of the image view (so either it will do nothing or set the wrong tag).
For saving the image, if you do want to save the image instead of the tag, you need to save it as data. You can store an image inside a dictionary no problem, but if you then want to store your dictionary as a plist you need to convert the image to NSData. You can get the image data using:
UIImageJPEGRepresentation(imageToSave, 0.8)
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;
}