I need to add on my MKMapView several annotations (example one for each city in a country) and I don't want to initializate every CLLocation2D with latitude and longitude of all cities. I think it is possible with arrays, so this is my code:
NSArray *latit = [[NSArray alloc] initWithObjects:#"20", nil]; // <--- I cannot add more than ONE object
NSArray *longit = [[NSArray alloc] initWithObjects:#"20", nil]; // <--- I cannot add more than ONE object
// I want to avoid code like location1.latitude, location2.latitude,... locationN.latitude...
CLLocationCoordinate2D location;
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
for (int i=0; i<[latit count]; i++) {
double lat = [[latit objectAtIndex:i] doubleValue];
double lon = [[longit objectAtIndex:i] doubleValue];
location.latitude = lat;
location.longitude = lon;
annotation.coordinate = location;
[map addAnnotation: annotation];
}
Ok, its all right if I leave ONE object in the NSArrays latit and longit, I have ONE annotation on the map; but if I add more than one object to the arrays app builds but crashes with EXC_BAD_ACCESS (code=1...). What's the problem, or what the best way to add several annotations without redundant code? Thank you!
You are using always the same annotation object, which is this:
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
Displace it in the for loop, or copy it before adding to the map.
Explanation: This is the annotation property found in the MKAnnotation protocol:
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
As you see it isn't copying the object, so if you add always the same annotation you would have duplicate annotations. If you add an annotation with coordinates (20,20) at time 1, then at time 2 you change the annotation coordinates to (40,40) and add it to the map, but it's the same object.
Also I don't suggest to put NSNumber objects inside it. Instead make an unique array and fill it of CLLocation objects, as they're made to store coordinates. The CLLocation class has this property:
#property(readonly, NS_NONATOMIC_IPHONEONLY) CLLocationCoordinate2D;
It's an immutable object too, so you need to initialise this property at the moment you create the object. Use the initWithLatitude:longitude: method :
- (id)initWithLatitude:(CLLocationDegrees)latitude longitude:(CLLocationDegrees)longitude;
So you could write a better version of your code:
#define MakeLocation(lat,lon) [[CLLocation alloc]initWithLatitude: lat longitude: lon];
NSArray* locations= #[ MakeLocation(20,20) , MakeLocation(40,40) , MakeLocation(60,60) ];
for (int i=0; i<[locations count]; i++) {
MKPointAnnotation* annotation= [MKPointAnnotation new];
annotation.coordinate= [locations[i] coordinate];
[map addAnnotation: annotation];
}
Related
I am new to objective-C and I am having harsh time with its semantics. Let say I have a bunch of SKSpriteNode stored in a NSMutableDictionary pointing to an enum status for each sprite code:
typedef enum PAWN_STATUS { OUT=0, MOVING, LANDED, IDLE } PAWN_STATUS;
and then
NSMutableDictionary* pawns = [[NSMutableDictionary alloc] init];
for(int i=0; i<10; ++i) {
SKSpriteNode *whiteButton = [SKSpriteNode spriteNodeWithImageNamed:#"white"];
[pawns setObject:[NSNumber numberWithInt:OUT] forKey:whiteButton];
}
then I change the status for some sprite, say
SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:location];
[pawns setObject:[NSNumber numberWithInt:LANDED] forKey:touchedNode];
But instead of changing the status of an existing SKSpriteNode, the last line is ADDING a new SKSpriteNode. So my question is why this is so? The hash is not defined for Nodes?
I would suggest a completely different approach to what you are trying to accomplish.
You can indeed associate user data to your nodes by using the following SKNode property:
#property(retain, nonatomic) NSMutableDictionary *userData
So, you do not need to handle your own dictionary associating a node to its status. You can just do:
touchedNode.userData = #{#"myNodeState":#LANDED};
Later on, you would do:
if ([touchedNode.userData[#"myNodeState"] intValue] == LANDED)
...
Notice also the use of "modern" Objective-C syntax making it easier.
Another unrelated improvement to readability would come from using static const * NSString instead of enums for your state:
In .h file:
typedef NSString* MyStateEnums;
const MyStateEnums MyStateEnumLanded;
In .m file:
const MyStateEnums kMyStateEnumLanded = #"landedNodeState";
So you can just do:
touchedNode.userData = #{#"myNodeState": kMyStateEnumLanded};
and more importantly:
if (touchedNode.userData[#"myNodeState"] == kMyStateEnumLanded)
EDIT:
in order to check if NSDictionary works with SKNodeSprite, do this simple test:
//-- your code:
NSMutableDictionary* pawns = [[NSMutableDictionary alloc] init];
for(int i=0; i<10; ++i) {
SKSpriteNode *whiteButton = [SKSpriteNode spriteNodeWithImageNamed:#"white"];
[pawns setObject:[NSNumber numberWithInt:OUT] forKey:whiteButton];
}
//-- testing:
for (SKSpriteNode* node in [pawns allKeys]) {
[pawns setObject:[NSNumber numberWithInt:LANDED] forKey:node];
}
for (SKSpriteNode* node in [pawns allKeys]) {
NSLog(#"Node %x state: %d", (int)node, [[pawns objectForKey:node] intValue]);
}
I'm making app with section tableview.The section header display district through keyArray. To row of table view, it will display all information including of name, address and distance. I can make it by below code.
Now, I'm going to sort ascending distance in tableview.
How can I sort it in 'cellForRowAtIndexPath' method?
My second thought is create NSArray at the outside of cellForRowAtIndexPath method. One is to load plist data and calculate distance. And then, sort it through NSSortDescriptor. If I go to this method, I am not sure how to populate it in section tableview correctly?
Can someone give me some idea or suggestion?
In key array, I use below code to create it in ViewDidLoad and put it into section header.
//location info draw from plist
NSArray*tempArray=[[NSArray alloc]init];
tempArray=[dataDictionary allKeys];
self.keyArray=[tempArray mutableCopy];
In cellForRowAtIndexPath, it calculate distance between target location and user location. And then display all information in table view row.
NSString*sectionHeader=[keyArray objectAtIndex:indexPath.section];
NSArray*sectionHeaderArray=[dataDictionary objectForKey:sectionHeader];
NSDictionary*targetLat=[sectionHeaderArray objectAtIndex:indexPath.row];
NSDictionary*targetLong=[sectionHeaderArray objectAtIndex:indexPath.row];
//location info draw from plist
CLLocation *targetLocation = [[CLLocation alloc] initWithLatitude:[[targetLat objectForKey:#"latitude"] floatValue] longitude:[[targetLong objectForKey:#"longitude"] floatValue]];
double distance = [self.myLocation distanceFromLocation:targetLocation]/1000;
UITableViewCell*cell=[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell==nil) {
cell=[[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitlereuseIdentifier:CellIdentifier] autorelease];
}
UILabel*nameLabel=(UILabel*)[cell viewWithTag:100];
UILabel*addLabel=(UILabel*)[cell viewWithTag:101];
UILabel*latLabel1=(UILabel*)[cell viewWithTag:102];
UILabel*longLabel1=(UILabel*)[cell viewWithTag:103];
UILabel*disLabel=(UILabel*)[cell viewWithTag:106];
NSDictionary*name=[sectionHeaderArray objectAtIndex:indexPath.row];
NSDictionary*address=[sectionHeaderArray objectAtIndex:indexPath.row];
nameLabel.text=[name objectForKey:#"name"];
addrLabel.text=[address objectForKey:#"address"];
latLabel1.text=[NSString stringWithFormat:#"%f",self.myLocation.coordinate.latitude];
longLabel1.text=[NSString stringWithFormat:#"%f",self.myLocation.coordinate.longitude];
disLabel.text=[NSString stringWithFormat:#"%0.2f km",distance];
return cell;
}
I jumped to conclusions and posted an answer that was flawed. I see now that you have a multitude of issues in your process.
You assign the same object, [sectionHeaderArray objectAtIndex:indexPath.row], to several different variables and are expecting different results from each. To start with, I would make the following changes to your existing code:
NSString*sectionHeader=[keyArray objectAtIndex:indexPath.section];
NSArray *locationsForSection = [self.dataDictionary objectForKey:sectionHeader];
NSDictionary *thisLocationInfo = [locationsForSection objectAtIndex:indexPath.row];
float thisLat = [[thisLocationInfo objectForKey:#"latitude"] floatValue];
float thisLong = [[thisLocationInfo objectForKey:#"longitude"] floatValue];
//location info draw from plist
CLLocation *targetLocation = [[CLLocation alloc] initWithLatitude:thisLat longitude:thisLong];
double distance = [self.myLocation distanceFromLocation:targetLocation]/1000;
UITableViewCell*cell=[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell==nil) {
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitlereuseIdentifier:CellIdentifier];
}
UILabel*nameLabel=(UILabel*)[cell viewWithTag:100];
UILabel*addLabel=(UILabel*)[cell viewWithTag:101];
UILabel*latLabel1=(UILabel*)[cell viewWithTag:102];
UILabel*longLabel1=(UILabel*)[cell viewWithTag:103];
UILabel*disLabel=(UILabel*)[cell viewWithTag:106];
NSDictionary*name=[sectionHeaderArray objectAtIndex:indexPath.row];
NSDictionary*address=[sectionHeaderArray objectAtIndex:indexPath.row];
latLabel1.text=[NSString stringWithFormat:#"%f",thisLat];
longLabel1.text=[NSString stringWithFormat:#"%f",thisLong];
disLabel.text=[NSString stringWithFormat:#"%0.2f km",distance];
return cell;
}
For your sorting issue, I would say to go back to your dataDictionary. Iterate through each of the sectionHeader arrays. Replace each array with a copy that is sorted by the distance from myLocation. You could add the key, distance, to each dictionary there and take that out of the cellForRowAtIndexPath method entirely.
-(NSDictionary *)dataDictionary
{
if (_dataDictionary) {
return _dataDictionary;
}
NSMutableDictionary *resultDictionary = [[NSMutableDictionary alloc] init];
for (NSString *sectionHead in self.keyArray) {
NSMutableArray *sectionArrayWithDistances = [[NSMutableArray alloc] init];
NSArray *thisSectionArray = [sourceDictionary objectForKey:sectionHead];
for (NSDictionary *theLocationDict in thisSectionArray) {
// Calculate the distance
float thisLat = [[theLocationDict objectForKey:#"latitude"] floatValue];
float thisLong = [[theLocationDict objectForKey:#"longitude"] floatValue];
CLLocation *targetLocation = [[CLLocation alloc] initWithLatitude:thisLat longitude:thisLong];
double distance = [self.myLocation distanceFromLocation:targetLocation]/1000;
// Insert modified dictionary into the resulting section array
NSMutableDictionary *thisLocationWithDistance = [NSMutableDictionary dictionaryWithDictionary:theLocationDict];
[thisLocationWithDistance insertObject:[NSNumber numberWithDouble:distance] ForKey:#"distance"];
[sectionArrayWithDistances addObject:[NSDictionary dictionaryWithDictionary:thisLocationWithDistance]];
}
// Sort the resulting array for this section and insert in dataDict
NSSortDescriptor *sortByDistance = [[NSSortDescriptor alloc] initWithKey:#"distance" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortByDistance, nil];
[sectionArrayWithDistances sortWithDescriptors:sortDescriptors];
[resultDictionary insertObject:[NSArray arrayWithArray:sectionArrayWithDistances] forKey:sectionHead];
}
// Set result and return
self.dataDictionary = [NSDictionary dictionaryWithDictionary:resultDictionary];
return _dataDictionary;
}
Total Air Code and turned out to be more than I bargained for. Possible errors in syntax and what not but this is the basic idea as I see it for your purposes.
I'm developing an iPhone app with annotations on a map, and I've finished inserting all the annotations. The code I've used for all the annotations (x100) is:
CLLocationCoordinate2D theCoordinate1;
theCoordinate1.latitude = 59.92855;
theCoordinate1.longitude = 10.80467;
MyAnnotation* myAnnotation1=[[MyAnnotation alloc] init];
myAnnotation1.coordinate=theCoordinate1;
myAnnotation1.title=#"Økern Senter - DNB";
myAnnotation1.subtitle=#"Økernveien 145, 0580 OSLO";
[mapView addAnnotation:myAnnotation1];
[annotations addObject:myAnnotation1];
What I'm wondering about is how can I get all these locations in a list that shows the closest annotations to the users location?
What you need to do is calculate the distance between the user and the annotations.
First off, in your MyAnnotation, add a variable keeping the distance value:
Add the following to MyAnnotation.h:
#property (nonatomic, assign) CLLocationDistance distance;
and the synthesize to the .m file ofcourse.
Second, in your mapView class (the one keeping the annotations ect) add the following code when you recieve a new location:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
[...]
for (MyAnnotation *annotation in self.mapView.annotations) {
CLLocationCoordinate2D coord = [annotation coordinate];
CLLocation *anotLocation = [[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude];
annotation.distance = [newLocation distanceFromLocation:anotLocation];
}
NSArray *sortedArray;
sortedArray = [self.mapView.annotations sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSNumber *first = [NSNumber numberWithDouble:[(MyAnnotation*)a distance]];
NSNumber *second = [NSNumber numberWithDouble:[(MyAnnotation*)b distance]];
return [first compare:second];
}];
[...]
}
You can now use the sortedArray as source for a tableView ect, which is sorted according distance from closest to longest distance.
Of course
I have this code:
-(void)handleLongPressGesture:(UIGestureRecognizer*)sender {
NSNumber* existingpoints = [[NSNumber alloc]init];
existingpoints =[NSNumber numberWithInt:0];
// This is important if you only want to receive one tap and hold event
if (sender.state == UIGestureRecognizerStateEnded)
{
[self.mapView removeGestureRecognizer:sender];
}
else {
do {
int z = 1;
existingpoints =[NSNumber numberWithInt:z];
// Here we get the CGPoint for the touch and convert it to latitude and longitude coordinates to display on the map
CGPoint point = [sender locationInView:self.mapView];
CLLocationCoordinate2D locCoord = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
// Then all you have to do is create the annotation and add it to the map
MKPointAnnotation *annotationPoint = [[MKPointAnnotation alloc] init]; annotationPoint.coordinate = locCoord;
NSString *latitude = [[NSString alloc] initWithFormat:#"%f",locCoord.latitude];
NSString *longitude = [[NSString alloc] initWithFormat:#"%f", locCoord.longitude];
annotationPoint.title = #"Event";
annotationPoint.subtitle = [NSString stringWithFormat:#"%# & %#", latitude, longitude];
[mapView addAnnotation:annotationPoint];
[[NSUserDefaults standardUserDefaults]setObject:latitude forKey:#"FolderLatitude"];
[[NSUserDefaults standardUserDefaults]setObject:longitude forKey:#"FolderLongitude"];
} while ([existingpoints intValue] == 0);
}
}
...but the problem is that when I hold, and then drag more than one pin is added. I want to add only one pin. So I tried the do method but it doesn't work. I can't understand, because when I executed the code I turn the value of the NSNumber to 1, and the while says = 0 to run the code.
Please Help!!
Your current code is prone to have quite a number of memory leaks. For example:
NSNumber* existingpoints = [[NSNumber alloc] init];
existingpoints = [NSNumber numberWithInt:0];
Is leaking because you leave the first instance of existingpoints with retain value of 1 and not freeing it anywhere. Unless you're using ARC. You can optimize the above code with just one instruction:
NSNumber* existingpoints = [NSNumber numberWithInt:0];
And retain it if you need to keep it somewhere (but i belive it's not the case).
Analyzing the code, I'd recommend NOT to use existingpoints as an NSNumber. Use an NSInteger instead (which is not an object, just a typedef to long).
Here's my rewritten code:
-(void)handleLongPressGesture:(UIGestureRecognizer*)sender {
NSInteger existingpoints = 0;
// This is important if you only want to receive one tap and hold event
if (sender.state == UIGestureRecognizerStateEnded) {
[self.mapView removeGestureRecognizer:sender];
}
else {
do {
int z = 1;
existingpoints = z;
// Here we get the CGPoint for the touch and convert it to latitude and longitude coordinates to display on the map
CGPoint point = [sender locationInView:self.mapView];
CLLocationCoordinate2D locCoord = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
// Then all you have to do is create the annotation and add it to the map
MKPointAnnotation *annotationPoint = [[MKPointAnnotation alloc] init];
annotationPoint.coordinate = locCoord;
NSString *latitude = [NSString stringWithFormat:#"%f",locCoord.latitude];
NSString *longitude = [NSString stringWithFormat:#"%f", locCoord.longitude];
annotationPoint.title = #"Event";
annotationPoint.subtitle = [NSString stringWithFormat:#"%# & %#", latitude, longitude];
[mapView addAnnotation:annotationPoint];
[[NSUserDefaults standardUserDefaults] setObject:latitude forKey:#"FolderLatitude"];
[[NSUserDefaults standardUserDefaults] setObject:longitude forKey:#"FolderLongitude"];
[annotationPoint release]; // Remove this if you're using ARC.
} while (existingpoints == 0);
}
}
Note that I've also changed the code for creating latitude and longitude for not to create any memory leaks when using ARC.
EDIT:
Further analyzing your code, I don't see why this method would be dropping two pins at once. Maybe you could check if your method is not being called twice?
More: Why do you have a do/while loop if you just want it to run once? (but maybe you're just paving your ground to further ahead)
I'm adding Annotations to a map, and in order to call them I need to store their Database ID value in an Dictionary
int i = 0;
...
while(sqlite3_step(statement) == SQLITE_ROW) {
int thisvenueid = sqlite3_column_int(statement, 4);
NSNumber *vid = [[NSNumber alloc] initWithInt:thisvenueid];
if (dict == nil) {
dict = [[NSMutableDictionary alloc] init];
}
[dict setObject:vid forKey:[NSNumber numberWithInt:i]];
}
And then I'm using it like this:
[self.mapView selectAnnotation:[dict objectForKey:selectedVenue] animated:YES];
However, this just crashes (Sigbart .. how I don't like Sigbart). I'm very thankful for any advise.
you are trying to select Annotation and passing NSNumber instead of object of MKPlacemark
please refer to documentation:
selectAnnotation:animated:
Selects the specified annotation and displays a callout view for it.
- (void)selectAnnotation:(id < MKAnnotation >)annotation animated:(BOOL)animated
Parameters
*annotation*
The annotation object to select.
animated
If YES, the callout view is animated into position.
Discussion
If the specified annotation is not onscreen, and therefore does not have an associated annotation view, this method has no effect.
Why are you using setObject? From what it looks like, you should be using addObject:
[dict addObject: vid forKey: [NSNumber numberWithInt:i]];
Forgive me if this is not the problem - haven't worked with Objective-C for some time.