How do I save multiple MapAnnotations using this code? - ios

In my mapview, the user can make his own map pin by tapping on the screen. He can set multiple pins. However, my code for saving only saves the last pin he has made and not the previous ones. How do I make it save the previous ones as well?
- (void)addPinToMap:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state != UIGestureRecognizerStateBegan)
return;
CGPoint touchPoint = [gestureRecognizer locationInView:self.mapView];
CLLocationCoordinate2D touchMapCoordinate =
[self.mapView convertPoint:touchPoint toCoordinateFromView:self.mapView];
NSMutableArray *pincoords = [[NSMutableArray alloc] init];
CLLocationCoordinate2D coordinate = { touchMapCoordinate.latitude, touchMapCoordinate.longitude };
[pincoords addObject:[NSValue valueWithBytes:&coordinate objCType:#encode(CLLocationCoordinate2D)]];
MapAnnotation *toAdd = [[MapAnnotation alloc]init];
toAdd.coordinate = touchMapCoordinate;
toAdd.title = #"Svampe Spot";
[self.mapView addAnnotation:toAdd];
//Save pin section..
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:pincoords forKey:#"saveCoords"];
[userDefaults synchronize];
}
EDIT: So i made some changes. I made an NSMutable array and added the coordinates to it. However, the app terminates when trying to make a pin due to this: [NSUserDefaults setObject:forKey:]: attempt to insert non-property list object.
Everytime i try to add coordinates to mutable array i seem to get that message, what am i doing wrong?

Every time you write a new pin to the user defaults, it overwrites the old one. Instead, write an NSArray or NSMutableArray to the user defaults and add/remove pins from it.
UPDATE:
First of all, the NSMutableArray needs to be copied from the user defaults, not alloc/inited. This way, the array will already contain the previously saved points in it:
NSMutableArray *pincoords = (NSMutableArray *)[[NSUserDefaults standardUserDefaults] objectForKey:#"saveCoords"];
This will return a mutable array with your previously saved coordinates.
As for the method you are using to store the coordinate in the array, I think you have the right idea, but I've personally never done it that way. NSArrays can only store objects, and CLLocationCoordinate2D is not an object (probably a C struct like NSRect, I've never used it before). It needs to be converted to a NSCoding compliant object, such as NSString, NSNumber, or NSDictionary. What you could do is create a function to manually enter the coordinate values (wrapped in an NSNumber) into a NSDictionary (using keys such as #"Lat" and #"Long" for your coordinates), and then store the NSDictionary in your mutable array.

Related

How to Store multiple dictionary in NSUserDefaults

I have two Viewcontrollers in FirstVC i build 5 UITextField for registration ,this TextField value are stroed in dictionary finally the dictionary stored in NSUserdefault then in SecondVC i want to show this data
My problem is that each time when i add new discretionary in NSUserdefault The old one dictionary was replaced
i want data of all dictionary.
below is code of my FirstVC
-(void)btnReg
{
//data add in disctionary
for (int i=1; i<=5; i++)
{
UITextField *txtTemp=(UITextField *)[self.view viewWithTag:i];
[discRege setObject:[NSNumber numberWithInt:count] forKey:#"no"];
[discRege setObject:txtTemp.text forKey:[arraylblName objectAtIndex:i-1]];
}
//dictionary add in nsuserdefault
[[NSUserDefaults standardUserDefaults]setObject:discRege forKey:#"ABC"];
[[NSUserDefaults standardUserDefaults]synchronize];
//push to SecondVc
secondViewController *objSec=[[secondViewController alloc]init];
[self.navigationController pushViewController:objSec animated:YES];
[self.navigationController setNavigationBarHidden:false];
}
below is code of my SecondVC
ArratTemp =[[NSUserDefaults standardUserDefaults]objectForKey:#"ABC"] ;
if (!ArratTemp )
{
ArratTemp =[[NSMutableArray alloc]init];
}
else
{
ArratTemp = [[[NSUserDefaults standardUserDefaults]objectForKey:#"ABC"]mutableCopy];
}
NSLog(#"%#",ArratTemp);
Every time you are using the same key and replacing the existing dictionary object...
// Using the same key will overwrite the last saved dictionary.
[[NSUserDefaults standardUserDefaults] setObject:discRege forKey:#"ABC"];
Instead of storing it as a dictionary, store it as an array of dictionaries. Whenever you add new registration, fetch the saved array, add new dictionary object into it and update the userDefaults with that array.
mutableArray = [[[NSUserDefaults standardUserDefaults]objectForKey:#"ABC"] mutableCopy];
[mutableArray addObject:discReg];
[[NSUserDefaults standardUserDefaults] setObject:mutableArraay forKey:#"ABC"];
[[NSUserDefaults standardUserDefaults] synchronize];
Hope it helps.
You're overwriting the same disctionary every time.
You have two solutions :
Solution 1.
Store different dictionaries under different keys, not all under "ABC". So in your for loop can use the index (i) to have multiple entries, instead of just ABC every time. Here it's a simple matter you can resolve yourself. Make sure to not store everything under the same Key, and you'll find them ;) For example, you could save under [NSNumber numberWithInt:i]and then browse your NSUserDefaults for 0, 1, 2, 3... and so on. I recommend against this btw, solution 2 is the way to go.
Solution 2.
Store all your dictionaries in an array, and then store the array in the NSUserDefaults.
For that, simply create an NSMutableArray that you keep empty, then add dictionaries in it !
NSMutableArray dataArray = [[NSMutableArray alloc]init];
for (int i=1; i<=5; i++)
{
//Creating new dictionary
NSMutableDictionary *currentDict = [[NSMutableDictionary alloc]init];
//Getting the text we want
UITextField *txtTemp =(UITextField *)[self.view viewWithTag:i];
NSString *text = txtTemp.text;
//This is here because you had it
[currentDict setObject:[NSNumber numberWithInt:count] forKey:#"no"];
//All dictionaries will have key = name of the Label,
//but you could change it to something static, like
// "Content" for example. It'll be easier to find later
[currentDict setObject:text forKey:[arraylblName objectAtIndex:i-1]];
//Adding that newly formed dictionary to the mutable array.
[dataArray addObject:currentDict];
}
//Adding the array containing dictionaries to the NSUSerDefaults
[[NSUserDefaults standardUserDefaults]setObject:dataArray forKey:#"ABC"];
Note : I'm not exactly sure what you're doing with the dictionaries in the for loop, but since you didn't show the code, I'm guessing its' not part of the question. With my answer you have enough information to make some corrections if needed. All you need to remember is :
Create a dictionary per answer, and not one for all
Put each dictionary in the same array
Save the array (containing all the dictionaries)

Sending CLLocationCoordinate2D to parameter of incompatible type my code in Xcode 4.5

I am new to Xcode. I am developing a vehicle tracking app for that I need to display more annotation points simultaneously. For this I need to store the coordinates in the array but it shows the error: Sending CLLocationCoordinate2D to parameter of incompatible type
my code is:
NSString *urlMapString=[NSString stringWithFormat:#"http://logix.com/logix_webservice/mapvehiclelist.php?uid=20&format=json"];
NSURL *urlMap=[NSURL URLWithString:urlMapString];
NSData *dataMap=[NSData dataWithContentsOfURL:urlMap];
if(dataMap!=NULL){
NSError *errorMap;
NSMutableArray *coordinates = [[NSMutableArray alloc]init];
NSDictionary *jsonMap = [NSJSONSerialization JSONObjectWithData:dataMap options:kNilOptions error:&errorMap];
NSArray *resultsMap = [jsonMap valueForKey:#"posts"];
for(int count=1;count<resultsMap.count;count++)
{
NSDictionary *resMap = [[resultsMap objectAtIndex:count]valueForKey:#"post"];
NSString *latOrgstring =[resMap valueForKey:#"latitude"];
latitude=[latOrgstring doubleValue];
NSString *longitudeString=[resMap valueForKey:#"longitude"];
longitude=[longitudeString doubleValue];
//Center
CLLocationCoordinate2D center;
center.latitude=latitude;
center.longitude=longitude;
[coordinates addObject:center]; //here it shows the error
}
}
Kindly advice me to solve this problem.
Thank you...
CLLocationCoordinate2D center isn't an object. You can store only objects in NSArray.
Use CLLocation for this.
CLLocation *location = [[CLLocation alloc] initWithLatitude:lat longitude:lon];
As already mentioned, you can only add objects to an NSArray.
CLLocationCoordinate2D is not an object -- it is a C struct.
The code you posted itself has one solution:
Create a NSDictionary with the latitude and longitude as key/value pairs and add the resulting dictionary object to the array.
Another approach is to create a CLLocation as shown in another answer.
A third idea is what #trojanfoe answered previously which is to wrap the struct in an NSValue.
However, note that there is a convenient NSValue addition specifically for MapKit that adds these two useful helper methods:
valueWithMKCoordinate: which returns an NSValue given a CLLocationCoordinate2D
MKCoordinateValue which returns a CLLocationCoordinate2D for the NSValue
For an example, see How to store CLLocationCoordinate2D?.
A final (at least in this answer) alternate approach which I would highly recommend instead of all the above is this...
Why do you want to store only the coordinates in an array?
Wouldn't you want to know what the coordinates are for (which vehicle, place, etc)?
Why not create a custom class that implements, say, the MKAnnotation protocol and has not only the coordinates (as a CLLocationCoordinate2D property) but also all the other information related to the coordinate? The class would be a subclass of NSObject<MKAnnotation>.
You could then conveniently access all the information in one place without using multiple arrays and trying to keep the objects in the same order so they all have the same index, etc.
You could then directly add these objects to an MKMapView since they implement MKAnnotation.
CLLocationCoordinate2D is a C struct, so you need to put it in NSValue container at first.
An NSValue object is a simple container for a single C or Objective-C
data item. It can hold any of the scalar types such as int, float, and
char, as well as pointers, structures, and object id references. Use
this class to work with such data types in collections (such as
NSArray and NSSet), key-value coding, and other APIs that require
Objective-C objects.
[coordinates addObject:[NSValue value:&coordinate withObjCType:#encode(CLLocationCoordinate2D)]];
Try this
CLLocationCoordinate2D new_coordinate = CLLocationCoordinate2DMake(latitude, longitude);
[points addObject:[NSValue valueWithMKCoordinate:new_coordinate]];
Or
CLLocation *location = [[CLLocation alloc] initWithLatitude:lat longitude:lon];
[coordinates addObject: location];
Can't u directly add coordinates to mapview that means Place MKAnnotation on Mapview, instead of taking coordinates to into array ?
SEE Below i commented with lines
for(int count=1;count<resultsMap.count;count++)
{
NSDictionary *resMap = [[resultsMap objectAtIndex:count]valueForKey:#"post"];
NSString *latOrgstring =[resMap valueForKey:#"latitude"];
latitude=[latOrgstring doubleValue];
NSString *longitudeString=[resMap valueForKey:#"longitude"];
longitude=[longitudeString doubleValue];
//Center
CLLocationCoordinate2D center;
center.latitude=latitude;
center.longitude=longitude;
-----------------------------------------
////// Annotation is MKAnnotation Subclass
Annotation * cartAnn = [Annotation new];
cartAnn.coordinate = center;
[self.mapView addAnnotation: cartAnn];
----------------------------------------------------
/////// [coordinates addObject:center]; //here it shows the error
}

Detecting duplicate custom object contained in a NSMutable Array

I've read every similar question, but have determined either I'm doing something stupid (possible) or I fail to grasp the NSArray method containsObject:
I'm trying to setup a UITableView that contains saved "favorites"; locations that are kept as a custom class called "MapAnnotations." This contains stuff like coordinates, title, an info field, and a couple of other parameters. I'm successfully saving/retrieving it from a NSUserDefaults instance, but can't seem to successfully detect duplicate objects held in my NSMutableArray.
Here's the relevant code:
-(void)doSetUp
{
//load up saved locations, if it exists
NSUserDefaults *myDefaults = [NSUserDefaults standardUserDefaults];
//if there are saved locations
if ([myDefaults objectForKey:#"savedLocations"]) {
NSLog(#"file exists!");
//get saved data and put in a temporary array
NSData *theData = [myDefaults dataForKey:#"savedLocations"];
//my custom object uses NSCode protocol
NSArray *temp = (NSArray *)[NSKeyedUnarchiver unarchiveObjectWithData:theData];
NSLog(#"temp contains:%#",temp);
//_myFavs currently exists as a NSMutableArray property
_myFavs = [temp mutableCopy];
}else{
NSLog(#"File doesn't exist");
_myFavs = [[NSMutableArray alloc]init];
}
//_currLoc is an instance of my Mapnnotations custom class
// which contains coordinates, title, info, etc.
if (_currLoc != nil) {
//if this is a duplicate of a saved location
if ([_myFavs containsObject:_currLoc]) {
//pop an alert
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Sorry..." message:#"That location has already been saved." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alert show];
}else{
//add location to end of myFavs array
[_myFavs addObject:_currLoc];
NSLog(#"myFavs now contains:%#",_myFavs);
//write defaults
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:_myFavs];
[myDefaults setObject:encodedObject forKey:#"savedLocations"];
[myDefaults synchronize];
}
}
}
I've tried enumerating through the _myFavs array, checking for matches on specific fields (get errors for enumerating through something mutable), tried to copy to a straight array... tried to use indexOfObject:..
You can use containsObject: method with custom objects that implement isEqual: method. Adding an implementation of this method to your Mapnnotations class will fix the problem:
// In the .h file:
#interface Mapnnotations : NSObject
-(BOOL)isEqual:(id)otherObj;
...
#end
// In the .m file:
#implementation Mapnnotations
-(BOOL)isEqual:(id)otherObj {
... // Check if other is Mapnnotations, and compare the other instance
// to this instance
Mapnnotations *other = (Mapnnotations*)otherObj;
// Instead of comparing unique identifiers, you could compare
// a combination of other custom properties of your objects:
return self.uniqueIdentifier == other.uniqueIdentifier;
}
#end
Note: when you implement your own isEqual: method, it is a good idea to implement the hash method as well. This would let you use the custom objects in hash sets and as NSDictionary keys.
Or you could use an NSOrderedSet (or mutable if needed) which is designed to do all your set membership functions, as well as having all your index type functions you'd expect from an NSArray.
You can transform it to array with - array when you need actual an NSArray version where an enumerable won't work.

Advice on MKAnnotation and NSString memory leak issues

I'm making an app that displays the user on the map along with multiple restaurant listings. When the user taps a pin, it stores the coordinates from the annotation and compares them against the users to ensure they are different. Once it is determined that they are different, it sends the coordinates of the business along with the coordinates of the user to Google to request directions. The code is working fine, but in order for it to do so I had to declare a few variables in ways that cause memory leaks. I'm hoping to clean the code up and learn where I erred and the proper way this should be handled.
So below is my code for getting the coordinates from the annotation that was tapped. If I attempt to initialize selectedAnnotation and allocate it's memory in say viewDidLoad by putting selectedAnnotation = [[MapLocation alloc] init]; Then it still shows up as a memory leak. And for reference, selectedAnnotation is a MapLocation(that conforms to MKAnnotation) variable, and as a property I have it (nonatomic, retain) and #synthesize(d).
I thought as long as I allocated it in memory, so long as I set its value to nil in viewDidUnload and released it in dealloc, that there should be no memory issues. What am I missing? Below is a screen shot of my memory leaks when I allocate the memory for selectedAnnotation in viewDidLoad along with the code provided below. If I have already allocated the memory, and check that the variable exists, why would it allocate the memory for the variable again? This happens for any restaurant pin I click on, but obviously not on the user's pin, because I have the code to release it in that case.
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
//NSLog(#"Selected annotation view");
// if we don't have the place holder already allocated
// lazy load the MapLocation placeholder variable
if(!selectedAnnotation)
{
selectedAnnotation = [[MapLocation alloc] init];
}
// save the annotation clicked
selectedAnnotation = view.annotation;
// if the annotation selected was is the same as the user's location
if((selectedAnnotation.coordinate.latitude == savedUserLocation.coordinate.latitude) && (selectedAnnotation.coordinate.longitude == savedUserLocation.coordinate.longitude))
{
// set it to nil and release it
selectedAnnotation = nil;
[selectedAnnotation release];
}
}
I'm having similar trouble with memory issues with the method below. I'm bringing in the JSON data from Google, to extract the address and coordinates of the user's location to display in the AnnotationView. I created all the necessary Arrays and Dictionaries to access the information, but once I allocate the memory for them and assign their values to savedUserLocation, if I try releasing say the NSDictionary variable userLocation, even as the last line of code in this method, the app crashes due to "[CFDictionary release]: message sent to deallocated instance 0x83ccb60". I'm pretty sure it's because I'm setting the values in savedUserLocation via pointer, and once the memory is released that information no longer exists, so what would be the proper way to allocate/release the memory to where I can access the information, without causing memory leaks? I've also tried using autorelease, but the same issues persist.
Here is the code that places the user pin.
- (void)fetchedData:(NSData *)responseData
{
//parse out the json data
NSError *error;
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData //1
options:kNilOptions
error:&error];
NSArray *results = [json objectForKey:#"results"]; //2
NSUInteger counter = [results count];
NSDictionary *userLocation = [[NSDictionary alloc] init];
//NSString *address = [[NSString alloc] init];
for(NSUInteger i=0; i < counter; i++)
{
userLocation = [results objectAtIndex:i];
// 2) Get the funded amount and loan amount
NSString *address = [[NSString alloc] initWithString:[userLocation objectForKey:#"formatted_address"]];
NSArray *types = [userLocation objectForKey:#"types"];
NSDictionary *geometry = [userLocation objectForKey:#"geometry"];
NSDictionary *location = [geometry objectForKey:#"location"];
float lat = [[location objectForKey:#"lat"] floatValue];
float lon = [[location objectForKey:#"lng"] floatValue];
CLLocationCoordinate2D newCoordinates;
newCoordinates.latitude = lat;
newCoordinates.longitude = lon;
// count how many types there are
NSUInteger numberOfTypes = [types count];
NSString *type = [[NSString alloc] init];
for(NSUInteger j=0; j < numberOfTypes; j++)
{
type = [types objectAtIndex:j];
if([type rangeOfString:#"street_address" options:NSCaseInsensitiveSearch].location != NSNotFound)
{
NSLog(#"%#", address);
if(!savedUserLocation)
{
savedUserLocation = [[MapLocation alloc] init];
}
[savedUserLocation setTitle:#"You are here!"];
[savedUserLocation setSubtitle:address];
[savedUserLocation setCoordinate:newCoordinates];
}
}
}
// determine which location is closest to the user by calling this function
MapLocation *closestLocation = [self determineClosestLocationToUser:allLocations locationOfUser:savedUserLocation];
// send in the user location and the closest store to them to determine appropriate zoom level and
// to center the map between the two
[self determineMapCenterAndZoomLevelFromUser:savedUserLocation andClosestLocation:closestLocation];
if(!pinDropped)
{
// add the annotation to the map and then release it
[mapView addAnnotation:savedUserLocation];
pinDropped = true;
}
}
Thanks for any and all help/suggestions/advice. I'd really like to understand the nuts and bolts of what I'm doing wrong, as I thought I had a pretty decent grasp on it.
In didSelectAnnotationView, you have this code:
selectedAnnotation = nil;
[selectedAnnotation release];
This causes a memory leak because you are setting selectedAnnotation to nil and then calling release on it.
The call to release does nothing because selectedAnnotation is nil at that point and a call to nil does nothing. This means the memory that had been allocated is never released but since the pointer variable has been set to nil, when didSelectAnnotationView is called again, your code allocates a new object.
You should switch the order of the two statements (call release first and then set to nil).
However, you don't need to alloc a new object just to keep a reference to the "selected annotation".
Declaring a regular ivar (not a retain property) and just setting it equal to the selected annotation should work.
In addition, the map view already has a property called selectedAnnotations which you should be able to use (so you don't need to maintain your own ivar or property). The map view's property is an NSArray but will always contain either 0 or 1 object(s). Be sure to check its count before accessing the object at index 0.
In fetchedData, you have several memory leaks caused by unnecessary alloc calls.
They are not needed because after calling alloc, you are then directly assigning a new reference to the pointer you just allocated memory for.
For example, userLocation is alloc'd before the for-loop but then inside the loop you directly point that variable to an object in the results array.
This means the memory that had originally been allocated for userLocation becomes abandoned with no reference to it. When you try to call release on userLocation, it is trying to release an object that was not allocated by code in fetchedData.
To fix at least userLocation, just declare the variable and don't alloc/init/release it.
The variables address and type (the NSString) have a similar issue.

Persistance storage for UIVIew

I am creating dynamically some "n" number of UIView Object in my application.I can able to drag, drop these objects to any position in the screen & chenge change their some of property .Now i want to save all these details with persistance storage ,so that whenever i launched the application nest time, i can able to see those already created object.
So what is the best solution for this?
Also is their any sample application available for this form which i can take reference?
I think you can do it this way.
// Create an array to store the properties
NSMutableArray *viewProperties = [NSMutableArray array];
// Loop through all the views
for (UIView *view in views) {
// Create a dictionary for each view
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
// Store the properties in the dictionary
[dict setValue:NSStringFromCGRect(view.frame) forKey:#"ViewFrame"];
...
// Add the dictionary to the array
[viewProperties addObject:dict];
}
// Finally add the array to persistence
[userDefaults setValue:viewProperties forKey:#"ViewProperties"];
Later you can get the array from persistence and create the views with the properties.
NSMutableArray *viewProperties = [userDefaults valueForKey:#"ViewProperties"];
for (NSDictionary *dict in viewProperties) {
UIView *view = [[UIView alloc] init];
NSString *frameAsString = [dict valueForKey:#"ViewFrame"];
view.frame = CGRectFromString(frameAsString);
// Get other properties from dictionary and set it to view
}

Resources