List of closest annotations in MapKit - objective-c

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

Related

Location manager giving really bad coordinates

I tried writing a coordinate using Core Location, and it's running nicely, only thing is, it's a vegetable when it comes to coordinates.
This is the code that gets the coordinates.
- (void)viewDidLoad
{
[super viewDidLoad];
self.locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
if (startingLocation == nil)
self.startingLocation = newLocation;
NSString *latitudeString = [[NSString alloc] initWithFormat:#"%g",newLocation.coordinate.latitude];
latitudeLabel.text = latitudeString;
NSString *longitudeString = [[NSString alloc] initWithFormat:#"%g",newLocation.coordinate.longitude];
longitudeLabel.text = longitudeString;
NSString *horizontalAccuracyString = [[NSString alloc] initWithFormat:#"%g",newLocation.horizontalAccuracy];
horizontalAccuraryLabel.text = horizontalAccuracyString;
NSString *altitudeString = [[NSString alloc] initWithFormat:#"%g",newLocation.altitude];
altitudeLabel.text = altitudeString;
NSString *verticalAccuracyString = [[NSString alloc] initWithFormat:#"%g",newLocation.verticalAccuracy];
verticalAccuracyLabel.text = verticalAccuracyString;
CLLocationDistance distance = [newLocation getDistanceFrom:startingLocation];
NSString *distanceString = [[NSString alloc] initWithFormat:#"%g",distance];
distanceTraveledLabel.text = distanceString;
}
It's giving me Lat: 37.7858 Long: -122.406 which is somewhere in USA, while I'm running the code from Europe, Romania...
Could it be because it's running from a simulator?
Also I know the locationManager:didUpdateToLocation:fromLocation is deprecated, but I tried doing it with:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
but I don't know how to access the elements of [location lastObject].
That lat & long combination is San Francisco, which is the home base for many commuting (via a posh luxury commuter bus) Apple employees.
You can set the location that your simulator is working with pretty easily:
Latitude & Longitude for Bucharest is "44.4325" & "26.103889", as far as I know.
As for accessing the elements of "[location lastObject]", the object(s) in that array will be a CLLocation object which has a "coordinate" property. If you get a "didUpdateLocations:" delegate method being called, that array will always have at least one location (which is what you access via "lastObject").

How to sort distance in sectional table view?

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.

Add MKAnnotations on a MKMapView from NSArray of coordinates

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];
}

MKMapView setSelected: animated: not working first time

When I call MKMapView method setSelected:animated: on an annotation, it doesn't work. But if I call it next time with a different annotation, it starts working.
Anyone have any ideas what could be possibly wrong?
Thanks
Code (2 relevant methods):
- (void)viewDidLoad {
[super viewDidLoad];
annotations = [[NSMutableArray arrayWithCapacity:30] retain];
for (NSDictionary *entry in entries) {
double lat = [[entry objectForKey:#"lat"] doubleValue];
double lon = [[entry objectForKey:#"lon"] doubleValue];
NSString *PLZ = [entry objectForKey:#"PLZ"];
NSString *name = [NSString stringWithFormat:#"%#%#", PLZ.length != 0? [NSString stringWithFormat:#"%#, ", PLZ] : #"", [entry objectForKey:#"Ort"]];
NSString *address = [NSString stringWithFormat:#"%# - %#", [entry objectForKey:#"Grund"], [entry objectForKey:#"Zeit"]];
CLLocationCoordinate2D coordinate;
coordinate.latitude = lat;
coordinate.longitude = lon;
MyLocation *annotation = [[[MyLocation alloc] initWithName:name address:address coordinate:coordinate] autorelease];
[mapView addAnnotation:annotation];
[annotations addObject:annotation];
}
NSLog(#"LOaded");
[self zoomToFitMapAnnotations];
}
- (void)showAnnotation:(int)i {
if (i <= [annotations count]) {
[mapView setSelectedAnnotations:[NSArray arrayWithObject:[annotations objectAtIndex:i]]];
}
}
The last method, showAnnotation is the one that gets called and an annotation is shown. Once again, it doesn't work with the annotation I call first time. No matter how many times i call it. But if I change the annotation, then it starts working, even with the annotation I called first (hope that makes sense).
Also, this works even the first time: [mapView setSelectedAnnotations:[NSArray arrayWithObject:[annotations objectAtIndex:i]]];
You shouldn't be calling that method youself.
According to the MKAnnotationView documentation:
You should not call this method directly. An MKMapView object calls
this method in response to user interactions with the annotation.
Instead try Dipen's suggested method (setSelectedAnnotations).
Use this may be help you
[mapView1 setSelectedAnnotations:[NSArray arrayWithObjects:addAnnotation,nil]];

Storing Sorted Arrays Causing EXC_BAD_ACCESS Error

Given a basic key/value array, I'm wanting to store two sorted arrays based on the original array: one array will be sorted by name, and the other by age.
The arrays seem to be sorting correctly when I output them to the log; however, when I try to access them elsewhere in the code, I'm receiving a EXC_BAD_ACCESS error.
Here's what I have so far:
// MyController.h
#interface MyController : UIViewController {
NSMutableArray *originalArray;
NSMutableArray *nameArray;
NSMutableArray *ageArray;
}
#property (nonatomic, retain) NSMutableArray *originalArray;
#property (nonatomic, retain) NSMutableArray *nameArray;
#property (nonatomic, retain) NSMutableArray *ageArray;
-(void)someRandomMethod;
#end
// MyController.m
#import "MyController.h"
#implementation MyController
#synthesize originalArray;
#synthesize nameArray;
#synthesize ageArray;
-(void)viewDidLoad {
// originalArray = (
// {
// "name" = "Sally";
// "age" = 18;
// },
// {
// "name" = "Chad";
// "age" = 26;
// },
// {
// "name" = "Carla";
// "age" = 24;
// },
// )
// sort by name
NSSortDescriptor *sortByNameDescriptor;
sortByNameDescriptor = [[[NSSortDescriptor alloc]
initWithKey:#"name"
ascending:NO] autorelease];
NSArray *sortByNameDescriptors = [NSArray arrayWithObject:sortByNameDescriptor];
nameArray = [originalArray sortedArrayUsingDescriptors:sortByNameDescriptors];
// sort by age
NSSortDescriptor *sortByAgeDescriptor;
sortByAgeDescriptor = [[[NSSortDescriptor alloc]
initWithKey:#"age"
ascending:NO] autorelease];
NSArray *sortAgeDescriptors = [NSArray arrayWithObject:sortByAgeDescriptor];
ageArray = [originalArray sortedArrayUsingDescriptors:sortByAgeDescriptors];
[super viewDidLoad];
}
-(void)someRandomMethod {
// whenever I try to access the sorted arrays, I receive the EXC_BAD_ACCESS error
[[nameArray objectAtIndex:0] valueForKey:#"name"];
[[ageArray objectAtIndex:0] valueForKey:#"age"];
}
-(void)viewDidUnload {
self.originalArray = nil;
self.nameArray = nil;
self.ageArray = nil;
[super viewDidUnload];
}
- (void)dealloc {
[originalArray release];
[nameArray release];
[ageArray release];
[super dealloc];
}
#end
Any ideas?
UPDATE: Thanks to #robin, by changing the code above to the code below, everything works great:
// sort by name
NSSortDescriptor *sortByNameDescriptor;
sortByNameDescriptor = [[[NSSortDescriptor alloc]
initWithKey:#"name"
ascending:NO] autorelease];
NSArray *sortByNameDescriptors = [NSArray arrayWithObject:sortByNameDescriptor];
nameArray = [[NSMutableArray alloc] initWithArray:[originalArray sortedArrayUsingDescriptors:sortByNameDescriptors]];
// sort by age
NSSortDescriptor *sortByAgeDescriptor;
sortByAgeDescriptor = [[[NSSortDescriptor alloc]
initWithKey:#"age"
ascending:NO] autorelease];
NSArray *sortAgeDescriptors = [NSArray arrayWithObject:sortByAgeDescriptor];
ageArray = [[NSMutableArray alloc] initWithArray:[originalArray sortedArrayUsingDescriptors:sortByAgeDescriptors]];
I dont think you know about this or not but when ever you create an object like string or array or dictionary, with init methods then the retain count gets incremented by 1
and if you create them like this
NSArray *anarray = [NSArray arrayWithArray:temp];
this will create an autorelease objects that will be released automatically after sometime.
So my advice don't use this type of code if you want to use the objects in more than 1 function. Always use init methods first to get the work done.
and if you are sure that the objects are not needed for the rest of the program than release them using release methode.