How to store a CLLocation object using NSKeyedArchiver? - ios

I am slightly confused why the following doesn't work. Can someone enlighten me?
I have one function that returns a filePath:
- (NSString *) lastLocationPersistenceFilePath {
NSString *filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"last_location"];
return filePath;
}
Then, in a method triggered by a button, I attempt to save a CLLocation object like so:
// Store this location so it can persist:
BOOL success = [NSKeyedArchiver archiveRootObject:_startLocation toFile:[self lastLocationPersistenceFilePath]];
if (!success) {
NSLog(#"Could not persist location for some reason!");
} else {
NSLog(#"New location has been stored.");
}
In my viewDidLoad:, I attempt to load it:
- (void)viewDidLoad
{
[super viewDidLoad];
// Retrieve last location:
CLLocation *decodedLocation = [NSKeyedUnarchiver unarchiveObjectWithFile:[self lastLocationPersistenceFilePath]];
if (decodedLocation) {
_startLocation = decodedLocation;
} else {
NSLog(#"Decoding failed.");
}
}
The archiving fails which, of course, means I can't decode anything either. I feel like I am missing something obvious/trivial... any pointers?

Simply, it is not possible to write to the the app's resource directory. Instead write to the app's document directory.
Code example"
- (NSString *) lastLocationPersistenceFilePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths firstObject];
NSString *filePath = [documentDirectory stringByAppendingPathComponent:#"last_location"];
return filePath;
}

Related

NSKeyedUnarchiver returns nil after subsequent app launch

EDIT: Have checked and rechecked file paths, code for archiving and unarchiving and no bugs found. Is there an issue with App File System between launches that I am not aware of?
I am using NSCoder to store images in my app. I create a unique file path, store that file path to Core Data, and archive an image using that same file path in the documents.
On the app's first launch, everything works as expected - the file paths saved in Core Data are used to unarchive the images stored in Documents. On subsequent launches, however, the unarchiver returns nil, and I get a crash.
ImageForArchiving.m - the object that is to be saved with init and encode methods
+ (ImageForArchiving *)createImageWithImage:(NSData*)data andDate:(NSDate*)date {
ImageForArchiving *archiveImage = [[ImageForArchiving alloc] init];
[archiveImage setDate:date];
[archiveImage setImageData:data];
return archiveImage;
}
- (id) initWithCoder: (NSCoder *)coder
{
if (self = [super init])
{
[self setDate: [coder decodeObjectForKey:#"date"]];
[self setImageData: [coder decodeObjectForKey:#"image"]];
}
return self;
}
- (void) encodeWithCoder: (NSCoder *)coder
{
[coder encodeObject:self.date forKey:#"date"];
[coder encodeObject:self.imageData forKey:#"image"];
}
Saving to archive, Core Data, creation of file path and unarchiving code
- (void)saveItemsAtFilePath:(NSString*)filePath andImageToArchive:(ImageForArchiving*)imageRecord {
[NSKeyedArchiver archiveRootObject:imageRecord toFile:filePath];
}
- (void)loadItemsWithFilePath:(NSString*)filePath {
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
ImageForArchiving *image = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(#"image %#", image.date);
} else {
NSLog(#"nothing saved");
}
}
-(void)saveToCoreDataWithFilePath:(NSString*)filePath{
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
NSManagedObjectContext *context = delegate.managedObjectContext;
NSManagedObject *photoAndDate = [NSEntityDescription insertNewObjectForEntityForName:#"Image" inManagedObjectContext:context];
[photoAndDate setValue:[NSDate date] forKey:#"date"];
[photoAndDate setValue:filePath forKey:#"image"];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
}
- (NSString *)createPathForDataFile
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
documentsPath = [documentsPath stringByExpandingTildeInPath];
NSError *error = nil;
if ([fileManager fileExistsAtPath: documentsPath] == NO)
{
[fileManager createDirectoryAtPath:documentsPath withIntermediateDirectories:YES attributes:nil error:&error];
}
NSString *stringForSaving = [[NSUUID UUID]UUIDString];
NSString *filePath = [documentsPath stringByAppendingPathComponent:stringForSaving];
return filePath;
}
Looping through Core Data array to load up data source. The addition of the unarchived object is w
I was encountered the same problem before. You write a image to a path and save it to the core data. Later read the image path, it is exactly same but no image is loaded.
So, Did you tested the app in the simulator? I don't know if it is intended or a kind of a bug but it would be working as you wanted on the real devices.
So try to run your app in the real devices and see if it works.
I just found out that in iOS 8 the file paths are reset after each app launch. The code above save the file name to Core Data and re-create the file path to Documents when calling for the file.
For instance, the below would create the file name.
- (NSString *)createPathForDataFile
{
NSString *stringForSaving = [[NSUUID UUID]UUIDString];
NSLog(#"filePath in CreatePathForDataFile %#", stringForSaving);
return stringForSaving;
}
Saving would occur by calling the path to the Documents folder and passing in the file name as the parameter filePath.
- (void)saveItemsAtFilePath:(NSString*)filePath andImageToArchive:(ImageForArchiving*)imageRecord {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *finalFilePath = [documentsPath stringByAppendingPathComponent:filePath];
[NSKeyedArchiver archiveRootObject:imageRecord toFile:finalFilePath];
}

Trying to load a saved NSMutableArray

This is the code I'm using to save an NSMutableArray "names" (after the user presses a save button), and I think it's working without problems, but I'm not sure what the corresponding code would be to then load my array when I reopen my app. Any help would be greatly appreciated!
- (IBAction)save:(id)sender {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex:0];
NSString *fullFileName = [NSString stringWithFormat:#"%#/temporaryArray", docDir];
[NSKeyedArchiver archiveRootObject:names toFile:fullFileName];
}
Well, you're halfway there. You've figured out how to archive the object. The question is, how do you unarchive it? As I explained in my comment, this is done with the very aptly named NSKeyedUnarchiver class.
Let's begin with a code sample:
#try {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex:0];
NSString *defaultPath = [docDir stringByAppendingPathComponent:#"temporaryArray"];.
self.yourArray = [NSKeyedUnarchiver unarchiveObjectWithFile: defaultPath];
if (!self.yourArray) {
NSLog(#"Error!");
} else {
// Success!
}
} #catch (NSException *exception) {
NSLog(#"Some error happened: %#", exception);
}
The NSKeyedUnarchiver class takes a path to a file containing the content archived by NSKeyedArchiver. It will then read this file and return the "root" object -- the object that you told NSKeyedArchiver to archive. It's that simple. (You should, of course, include error handling, which I gave a brief example of above.)
If you want another resource, you can read this great introductory article by the famous Mattt Thompson, which gives a good explanation of the concepts behind the class.
Hope this helps.
If your array's contents are all property list objects (NSString, NSData, NSArray, or NSDictionary objects) then you can do saving and reading in a way other than using NSKeyedArchiever/NSKeyedUnarchiever:
- (IBAction)save:(id)sender
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex:0];
NSString *fullFileName = [docDir stringByAppendingPathComponent:#"temporaryArray"];
[names writeToFile:fullFileName atomically:YES];
}
- (NSMutableArray*)readNames
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex:0];
NSString *fullFileName = [docDir stringByAppendingPathComponent:#"temporaryArray"];
return [NSMutableArray arrayWithContentsOfFile:fullFileName];
}
- (IBAction)save:(id)sender
{
// check for previous data persistancy.
NSArray *arrData = [[NSUserDefaults standardDefaults]objectforkey:#"Paths"];
if(arrData == nil)
{
// Maintain your old data and update it in new one to store the same.
NSMutableArray *arrTempData = [NSMutableArray arrayWithArray:arrData];
//Add Some data to old array based on your bussiness logic.
[arrTempData add object:#"some data"];
// Update in User Defaults
[[NSUserDefaults standardDefaults]setobject:[NSArray arrayWithArray:arrTempData] forkey:#"Paths"];
}
}
Somewhere in your code,put below code to get the data,
NSArray *arrSavedData = [[NSUserDefaults standardDefaults] objectforkey:#"Paths"];

Objective-C read/write to plist file //after trying 9999 different solutions

Like I said in the subject, I'm facing some problems with both read and write to a .plist file. I already read a lot of possible implementations from other threads here and from a book, but still I can't make this to work, so I'd like to ask for your opinion. Right now this goes like this:
I have an array of movies arrayMovies in my shared instance data manager. I work my whole app based on this array. It's always "reachable" from every part of the app with just this:
[[iCultureDataManager sharedInstance]arrayMovies];
So when the app finishes loading, I initialize the arrayMovies from an array that is supposed to be in the .plist file, like this:
-(NSString* ) getFilePathForMovies{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentsDirectory = [path objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingString:#"movies.plist"];
return filePath;
}
- (NSArray *) getMovies{
NSString *myPath = [self getFilePathForMovies];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:myPath];
NSArray* movies;
if(fileExists){
movies = [[NSArray alloc ]initWithContentsOfFile:myPath];
}
return movies;
}
and in the init I just
_arrayMovies = [NSMutableArray arrayWithArray:[self getMovies]];
One thing I don't understand is why does the BOOL fileExists always returns false, like it's not finding the .plist file.
To save the array for the .plist file I do:
- (void)applicationDidEnterBackground:(UIApplication *)application {
[self addToMovies];
NSLog(#"DidEnterBackground: Adding to Movies...");
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[self addToMovies];
NSLog(#"applicationWillEnterForeground: Adding to Movies...");
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
/*
Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
*/
}
- (void)applicationWillTerminate:(UIApplication *)application {
NSLog(#"applicationWillTerminate: Adding to Movies...");
[self addToMovies];
}
- (void) addToMovies{
NSString *myPath = [self getFilePathForMovies];
[[[iCultureDataManager sharedInstance]arrayMovies] writeToFile:myPath atomically:YES];
}
-(NSString* ) getFilePathForMovies{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentsDirectory = [path objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingString:#"movies.plist"];
return filePath;
}
I think it's pretty much this for the matters but if I can help you with some more information feel free to say, cause I really don't understand what is going on. I don't know if I'm writing to file wrong or reading it wrong or whatever else I'm doing wrong.
Thank you very much for your time and help,
PS: now I'm at the point where the fileExists always returns -1... :S
You imply that you are initializing your array with a list of movies that are in the plist file. That would imply that you are starting with a plist file. To access the read-only plist file that exists in your bundle, you use a different path like this:
-(NSString *)getOriginalFilePathForMovies {
NSString* plistPath = [[NSBundle mainBundle] pathForResource:#"originalMovies" ofType:#"plist"];
contentArray = [NSArray arrayWithContentsOfFile:plistPath];
}
Then to access the writable plist you need a path like you are using:
-(NSString* ) getFilePathForMovies{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentsDirectory = [path objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingString:#"movies.plist"];
return filePath;
}
So I would recommend doing this to initialize your array:
- (NSArray *)getMovies {
NSString *myPath = [self getFilePathForMovies];
BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPath:myPath];
NSArray* movies;
if(fileExists){
movies = [[NSArray alloc]initWithContentsOfFile:myPath];
} else {
movies = [[NSArray alloc]initiWithContentsOfFile:[self getOriginalFilePathForMovies]];
return movies;
}
Instead of stringByAppendingString:, you should be using stringByAppendingPathComponent:. Otherwise, your path will look like .../Documentsmovies.plist (I think).
EDIT: Oh, someone already said in the comments!

Storing and reading files from Documents directory iOS 5

In my game, when a level is completed the app stores a "1" in a file in the Documents directory of the app. When the game then loads, a player can only play a level if the previous level has been completed. When I test the game via Xcode and on a device the app works properly and a level cannot be played until the previous level has been completed. However, when the app was approved and released on the App Store, the app behaves as if each level has been completed (no locked levels). I can't figure this one out and would appreciate someone's help! The devices I'm testing on are all iOs 5.0 or higher.
Below is the code that saves the completed level in the Documents directory:
NSMutableData* data = [[NSMutableData alloc] init];
NSKeyedArchiver* coder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
NSString *levelString = [NSString stringWithFormat:#"Level%d",level];
[coder encodeInteger:1 forKey:levelString];
[coder finishEncoding];
NSString *levelString2 = [NSString stringWithFormat:#"Level%d.save",level];
///
NSFileManager *filemgr;
NSString *dataFile;
NSString *docsDir;
NSArray *dirPaths;
filemgr = [NSFileManager defaultManager];
// Identify the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
// Build the path to the data file
dataFile = [docsDir stringByAppendingPathComponent:levelString2];
// Check if the file already exists
if ([filemgr fileExistsAtPath: dataFile])
{
[[NSFileManager defaultManager] removeItemAtPath:dataFile error:nil];
}
[data writeToFile:dataFile atomically:YES];
[coder release];
[data release];
}
#catch (NSException* ex)
{
CCLOG(#"level save failed: %#", ex);
}
Below is the code that reads the Document directory to see if the level has been completed:
if ([self loadCompletedLevels:6] == 1) { //// level gets unlocked **** }
-(int) loadCompletedLevels:(int)theLevel; {
int isLevelCompleted; //1 = completed
NSString* kSaveFile = [NSString stringWithFormat:#"Level%d.save",theLevel];
NSString *levelString = [NSString stringWithFormat:#"Level%d",theLevel];
#try
{
NSFileManager *filemgr;
NSString *dataFile;
NSString *docsDir;
NSArray *dirPaths;
filemgr = [NSFileManager defaultManager];
// Identify the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
// Build the path to the data file
dataFile = [docsDir stringByAppendingPathComponent:kSaveFile];
if ([[NSFileManager defaultManager] fileExistsAtPath:dataFile])
{
NSData* data = [[NSData alloc] initWithContentsOfFile:dataFile];
if (data && [data length] > 0)
{
NSKeyedUnarchiver* decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
isLevelCompleted = [decoder decodeIntForKey:levelString];
[decoder release];
}
[data release];
}
if (isLevelCompleted == 1) {
levelCompleted = YES;
}
}
#catch (NSException* ex)
{
levelCompleted = NO;
}
return isLevelCompleted; }
You should probably use a different approach for storing your data, but the real problem is that you are not initializing the return value, isLevelCompleted. It is on the stack, and does not have a default value. It starts out with whatever happens to be at that stack location.
So, if you don't set it, it will have an arbitrary value.
Also, you should probably use BOOL for a boolean value, but if you make this:
int isLevelCompleted = 0; //1 = completed
you will initialize it to "false" so it must be explicitly changed to "true" by your code.

Load data EXC BAD ACCESS (only device)

When my custom data is loading on iPhone (3G, with 3.1) I get Exc Bad Access
in this line:
NSMutableArray* dataArr = [NSKeyedUnarchiver unarchiveObjectWithFile:pathGG]; //=EXC BAD ACCESS
On Ipad, and on simulator work
I saving data in simulator (to documentsDirectory path), then replace data to project, and load from [NSBundle mainBundle]
In array I use NSValue for store CGPoint.
Full source:
-(void) SaveData:(NSMutableArray*)dataLevel {
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentsDirectory = [paths objectAtIndex:0];
NSString* fileName = #"data.dat";
NSString* pathGG = [documentsDirectory stringByAppendingPathComponent:fileName]; // retain];
BOOL isWrite = [NSKeyedArchiver archiveRootObject:dataLevel toFile:pathGG];
if(isWrite) NSLog(#"YES");
else NSLog(#"!!!");
}
+(NSMutableArray*) LoadData {
NSString* fileName = #"data.dat";
NSString* pathGG = [[NSBundle mainBundle] pathForResource:fileName ofType:#"dat"]; // retain];
NSMutableArray* dataA = [NSKeyedUnarchiver unarchiveObjectWithFile:pathGG]; //EXC BAD ACCESS
return dataA;
}
If I am using follow way(saving/loading same method), I get EXC BAD ACCESS too:
NSKeyedUnarchiver* decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSArray* dataArr = [decoder decodeObjectForKey:#"dataLevel"]; //EXC BAD ACCESS
Try this:
NSString* pathGG = [[documentsDirectory stringByAppendingPathComponent:fileName] retain];
Then when you are finished with it:
[pathGG release]
SOLUTION: Instead of store CGPoint in NSValue, use CGPointFromString(after NSStringFromCGPoint), and then add string to array.
NSString* positionStr = NSStringFromCGPoint(someCGPoint);
[someArray addObject:position];
// Now, saving array to file
...
//After loading array from file
CGPoint positon = CGPointFromString([someArr objectAtIndex:0]);

Resources