I'm attempting to build an app which will track a route, then store the route in parse.com so I can overlay the route taken by a user using MKpolyline.
I'm very new to Objective-c and IOS development, so please excuse my ignorance!!
I'm stuck when I try to save the route taken, then send/save the location array so I can rebuild the MKpolyline on the next view controller which is opened when the user completes the activity.
I'm not sure whether to save the location array to NSUserDefaults or save it to core data. At the moment I am converting the Array to an NSValue and the saving it to NSUserDefaults like so:
count = [self.locations count];
CLLocationCoordinate2D coordinates[count];
for (NSInteger i = 0; i < count; i++) {
coordinates[i] = [(CLLocation *)self.locations[i] coordinate];
NSValue *locationValue = [NSValue valueWithMKCoordinate:coordinates[i]];
[_locationsArray addObject:locationValue];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:totalDistance forKey:#"totalDistance"];
[defaults setObject:_locationsArray forKey:#"mapOverlay"];
// [defaults setDouble:_totalTime forKey:#"totalTime"];
[defaults setObject:avgSpeedToBeSaved forKey:#"averageSpeed"];
[defaults setObject:totalCalories forKey:#"totalCalories"];
[defaults synchronize];
Is this the right way to do this? And how do I rebuild the locations Array.
If anyone could point me in the right direction it would be greatly appreciated.
I've now changed my code to what was suggested by manecosta to rebuild the CLLocationCoordinates to create an MKPolyline, but my issue now is that the array is Null from where I start to convert into an NSValue. I am unable to figure out why this is, is there something wrong with the way I'm building LocationsArray in the first place?
Yes, I guess you're doing it right and to rebuild just do the opposite, which should be something like:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *locationsArray = [defaults objectForKey:#"mapOverlay"];
CLLocationCoordinate2D coordinates[[locationsArray count]];
NSInteger i = 0;
for(NSValue *locationValue in locationsArray){
coordinates[i] = [locationValue MKCoordinateValue];
i++;
}
About the fact that you're using User Defaults to store tons of data. I don't really know what is correct, but I'll tell you that I've previously used it to store the cache of my app which were quite big arrays and dictionaries and it never failed me.
You can get object from NSUserDefaults by using objectForKey:
As you have [defaults setObject:totalDistance forKey:#"totalDistance"];.
Now if you want to get the totalDistance back from the NSUserDefaults` then you can use following code:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *arrayLocation = [defaults objectForKey:#"totalDistance"];
But a piece of advise for you is not to use NSUserDefaults for your project. NSUserDefaults should only be used to store little amount of data (It's just a personal opinion.). Your locationArray is going to be quite large. Right?
A nice comparison between the two options NSUserDefaults & CoreData
What are the limitations of NSUserDefaults?
I'm posting answer here (from above link - in case the question gets unavailable in future time - who knows !!)
NSUserDefaults offers a trivial learning curve and thread safe
implementation.
Otherwise I've found Core Data superior in every way. Especially with
regards to configuring default values and migration routines.
EDIT: I actually haven't tried but it seems to be this way..
Tell me if it does not work.
To get back NSArray from NSValue
NSValue *value = [defaults objectForKey:#"mapOverlay"];
NSArray *arrayTemp;
[value getValue:&arrayTemp];
Related
I am trying to save an array of objects into an NSUserDefault without success. When I log out the array before the attempt it is full of object. However, when I try to log out the NSUserDefault it is NULL. Can anyone see what I might be doing wrong? Thanks for any suggestions:
Items *myItems = [mutableFetchedObjects mutableCopy];
NSLog(#"my Items%#",myItems);//LOGS OUT LONG LIST OF ITEMS
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:myItems];
[currentDefaults setObject:data forKey:#"myItems"];
[currentDefaults synchronize];
Items *myRetrievedItems = [[[NSUserDefaults standardUserDefaults] arrayForKey:#"myItems"] mutableCopy];
NSLog(#"my Retrieved Items%#",myRetrievedItems); //LOGS OUT AS NULL
As the other answers mentioned, it is because your array is not complying to the NSDictionary types (string, binary, bool, etc). Your members of array is of custom types therefore it cannot be saved. What you need to do is convert your array to binary first and then save it.
You have to unarchive your data first at the time of retrieving back. You are directly accessing the data. This won't work. You can do it the similar way you are archiving the data
NSData *dataObj = [currentDefaults objectForKey:#"myItems"];
Items *myRetrievedItems = [NSKeyedUnarchiver unarchiveObjectWithData:dataObj];
For more reference, you can consider this answer.
Hope this helps.
Thanks!
Your access value method is wrong.
You can get the array in following code:
Items *myRetrievedItems = [[[NSUserDefaults standardUserDefaults] objectForKey:#"myItems"] mutableCopy];
In my project I am using NSUseDefaults for store data with the different objects.
NSUserDefaults *defaults1=[NSUserDefaults standardUserDefaults];
//---- I have set object for this
[defaults1 synchronize];
NSUserDefaults *defaults2=[NSUserDefaults standardUserDefaults];
//---- I have set object for this
[defaults2 synchronize];
Now I want clear all keys data only for defaults2, not for defaults1. So whenever I am applying below code:
NSDictionary *defaultsDictionary = [defaults2 persistentDomainForName: appDomain];
for (NSString *key in [defaultsDictionary allKeys]) {
NSLog(#"removing user pref for %#", key);
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
}
Above code have remove value for defaults2 but also for defaults1. But I don't want to remove objects for defaults1. So please help me out.
NSUserDefaults is like a singelton class so it will always return the same shared system object.
You can store multiple objects using multiple keys and can delete/remove objects against those keys.
If you have read a doc about NSUserDefaults standardUserDefaults you should know that standardUserDefaults Returns the shared defaults object. and actually defaults1 and defaults2 the same.
You can store keys and then delete only those keys like:
NSUserDefaults *defaults1=[NSUserDefaults standardUserDefaults];
//---- I have set object for this
[defaults1 synchronize];
[[defaults1 dictionaryRepresentation] allKeys]; // use this keys for deleting
When the following code executes, it logs "0" even though the indexPath.row selected is "1".
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSInteger location = indexPath.row;
[userDefaults setInteger:location forKey:#"savedlocations"];
NSInteger location2 = [userDefaults integerForKey:#"savedlocation"];
NSLog(#"l: %ld", location);
You've got several things that need to be fixed:
You're saving with the key savedlocations, but retrieving it with savedlocation. Note that the first is plural.
You're logging location, not location2, which is what's being pulled from NSUserDefaults. location2 will always be 0, because of the point above.
Edit: I had a third point here about calling synchronize, but it turns out that that is irrelevant to this situation, as explained by rmaddy in the comments below.
Other than those quick fixes, though, your configuration ought to work.
You are looking for the key "savedlocation" instead of "savedlocations".
EDIT:
0 is being returned by the method, which is what is returned for an NSInteger that can't be found. My bad on the weird wording.
I am trying to wrap my head around iCloud storage. I have read through the iCloud Design Guide and some questions here on SO. Here is my scenario:
I have a class that only has an NSMutableArray...that collection holds my custom objects which all adhere to NSCoding and saving locally works perfectly. That class is MasterList and has the property masterList, brilliant - I know :-p. When I looked on how to start implementing iCloud I thought KVS would be great, since my data footprint is extremely small. When I attempted this, I kept getting:
[NSUbiquitousKeyValueStore setObject:forKey:]: Attempt to insert non-property value <my custom objects>...
After seeing why that was, it seems you can only store scalar and P-List types with KVS. So I moved to using UIDocument and am struggling with it.
Bottom line -
For custom objects, do you have to use UIDocument, or is it possible to use KVS?
and
If I must use UIDocument, did any of you read a tutorial that was simple in nature (maybe stored a few props and loaded them back, maybe in a sample project?) that made it click for you?
Below is my code for using KVS if that helps at all. Not production or anything, just trying to get it to work:
//Load it
+(MasterList *)decodeMasterList:(MasterList *)objectToDecode
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSURL *dataFile = [self pathForDocumentsFile:kFilePathAllLists];
NSString *filePath = [dataFile path];
objectToDecode = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
//First time runnign the app :-)
if (!objectToDecode) objectToDecode = [[MasterList alloc] init];
if ([defaults boolForKey:kIsUsingiCloud])
{
objectToDecode.masterList = [[[NSUbiquitousKeyValueStore defaultStore] arrayForKey:kiCloudPath] mutableCopy];
//If we just started and nothing is there
if (!objectToDecode.masterList)
{
objectToDecode.masterList = [[NSMutableArray alloc] initWithObjects:[CustomList new], nil];
}
}
return objectToDecode;
}
//Save it
+(BOOL)encodeMasterList:(MasterList *)objectToEncode
{
#try
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults boolForKey:kIsUsingiCloud])
{
[[NSUbiquitousKeyValueStore defaultStore] setArray:objectToEncode.masterList forKey:kiCloudPath];
[[NSUbiquitousKeyValueStore defaultStore] synchronize];
}
NSURL *dataFile = [FileSystemHelper pathForDocumentsFile:kFilePathAllLists];
NSString *filePath = [dataFile path];
[NSKeyedArchiver archiveRootObject:objectToEncode toFile:filePath];
}
#catch (NSException *exception)
{
return NO;
}
return YES;
}
It's true that the key-value store can only hold property list types. But you mentioned the objects in your array all conform to NSCoding, so that's not a major problem. You just need to apply NSCoding to convert your objects to/from NSData, and store that.
Encode using
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:myObject];
Decode using
NSData *data = // from key-value store
MyClass *myObject = [NSKeyedUnarchiver unarchiveObjectWithData:data];
However if you're using UIDocument, the key-value store is probably not the best way to go. You can also store and sync documents directly in iCloud via NSFileManager and NSMetadataQuery. Apple provides pretty good documentation on this, and it would sync the document in-place instead of requiring you to convert it back and forth. Plus of course, the key-value store has a very low total size limit while documents are only limited by the capacity of the user's iCloud account.
I am using this helper class: Secure-NSUserDefaults
I'm running into a problem where the validationhash differs from the stored hash for a NSDictionary when I modify the latter and try saving it again.
Here is my code:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL valid = NO;
NSDictionary * modDict = [defaults secureDictionaryForKey:aKey valid:&valid];
if (!valid) {
//handle
}
for (NSString * modKey in modDict) {
NSMutableArray * modArray = [[modDict objectForKey:modKey] mutableCopy];
NSString * newValue = #"newValue";
[modArray replaceObjectAtIndex:0 withObject:newValue];
NSMutableDictionary *newModDict = [modDict mutableCopy];
[newModDict setObject:modArray forKey:modKey];
[defaults setSecureObject:newModDict forKey:aKey];
[defaults synchronize];
}
When I run this code once, kill the app, and start it again, all works as it should, and the validationhash matches. It always matches correctly on the first run, in fact. In other words: my code saves out the secure dictionary and the hash matches on the subsequent app run just fine (once).
BUT - if I run the same code a second time without killing the app between runs, the validationhash is different, and the &valid check fails.
I was able to trace down the problem to this line:
[newModDict setObject:modArray forKey:modKey];
If I comment it out, it works fine all the time, but then of course, the modification I am trying to make isn't made at all, I simply copy the Dictionary "as is".
I'm struggling to figure out what I am doing wrong or how to modify and re-save the NSDictionary so that the hash matches.
Thanks in advance for any help!
LOL! Funny how sometimes writing down a question in a concise way triggers new ideas. It just occurred to me what I had to do, and just now solved it like this:
[newModDict setObject:[modArray copy] forKey:modKey];
i.e. I had to make the edited NSArray non-mutable first.
Maybe someone will benefit from the answer. I'm too embarrassed to mention how long I worked on this last night.