An instance 0x1d58be70 of class MKPlacemark was deallocated - iphone

When I tried to add annotation on mapview using MKPlacemark using this code:
for(int i = 0; i < [array count]; i++){
UsersModel *model = [array objectAtIndex:i];
CLLocationCoordinate2D newCoordinate;
newCoordinate.latitude = model.coordinate.latitude;
newCoordinate.longitude = model.coordinate.longitude;
NSLog(#"%f",model.coordinate.latitude);
NSLog(#"%f",model.coordinate.longitude);
//mPlaceMark = [[MKPlacemark alloc] initWithCoordinate:newCoordinate addressDictionary:nil];
MKPlacemark *placeMark = [[MKPlacemark alloc] initWithCoordinate:newCoordinate addressDictionary:nil];
//[self.mapView addAnnotation:mPlaceMark];
[self.mapView addAnnotation:placeMark];
//[placeMark retain];
NSLog(#"%#", self.mPlaceMark);
}
Then it gives an error on my console:
An instance 0x1d58be70 of class MKPlacemark was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
<NSKeyValueObservationInfo 0x1d53de10> (
<NSKeyValueObservance 0x1d58af30: Observer: 0x1d576690, Key path: coordinate, Options: <New: NO, Old: NO, Prior: YES> Context: 0x0, Property: 0x1d58af90>
)

Seemingly, you forgot to remove some abserver. Try using this code in your class dealloc method.
for (id <MKAnnotation> annotation in self.mapView.annotations)
{
[[self.mapView viewForAnnotation:annotation] removeObserver:self]; // NOTE: remove ALL observer!
}
You should also remember to nil your map delegate:
self.mapView.delegate = nil;

Related

Google Maps markers not removing iOS

I'm running a thread to fetch drivers location every 10 seconds and want to remove the added markers from the map but it doesn't work..
My code:
-(void)APiResponse:(id)returnJson
{
[googleMapsDriverPin setMap:nil];
googleMapsDriverPin = nil;
NSMutableArray *driverPins = [[NSMutableArray alloc]init];
for (int x = 0; x < [[returnJson valueForKey:#"drivers"] count]; x++) {
CLLocation *driverLocations = [[CLLocation alloc]initWithLatitude:[[[[returnJson valueForKey:#"drivers"] objectAtIndex:x] valueForKey:#"driver_latitude"] doubleValue] longitude:[[[[detail valueForKey:#"drivers"] objectAtIndex:x] valueForKey:#"driver_longitude"] doubleValue]];
[driverPins addObject:driverLocations];
}
for (CLLocation *newLocation in driverPins) {
googleMapsDriverPin = [[GMSMarker alloc] init];
[googleMapsDriverPin setPosition:newLocation.coordinate];
[googleMapsDriverPin setAnimated:YES];
[googleMapsDriverPin setTitle:#"title"];
[googleMapsDriverPin setSnippet:#"snippet"];
[googleMapsDriverPin setIcon:[GMSMarker markerImageWithColor:[UIColor blackColor]]];
[googleMapsDriverPin setMap:googleMaps];
}
}
It just keeps adding and adding every 10 seconds and not removing, please help!
Thanks!
Its a kind of quick and dirty option but if you wanted to go that way GMSMarker has a userData property which you could use to tag the driver pins
- (void)apiResponse:(id)returnJson
{
for (GMSMarker *pin in self.googleMaps.markers) {
if (pin.userData == #"Driver Pin"){
pin.map = nil;
}
}
...
for (CLLocation *newLocation in driverPins) {
googleMapsDriverPin = [[GMSMarker alloc] init];
...
[googleMapsDriverPin setUserData:#"Driver Pin"];
}
}
Update:
[self.googleMapsView clear];
On the based on pin id you can also delete pin.
Here deletePinId integer is for selected pin id.
for(GMSMarker *pin in self.mapView_.markers) {
NSLog(#"pin.userData : %#",pin.userData);
int pinId1 = [[pin.userData valueForKey:#"pin_id"] integerValue];
if(deltePinId == pinId1 ){
pin.map = nil;
}
}
you currently only store ONE marker, but you want to add N markers -- so (as saxon said) you need an array to hold all the pins :)
#interface YouClass
...
#property(nonatomic, retain) NSMutableArray *googleMapsDriverPins;
#end
#implementation YourClass
...
-(void)APiResponse:(id)returnJson
{
for(GMSMarker *pin in self.googleMapsDriverPins) {
pin.map = nil;
}
self.googleMapsDriverPins = nil;
NSMutableArray *driverPins = [[NSMutableArray alloc]init];
for (int x = 0; x < [[returnJson valueForKey:#"drivers"] count]; x++) {
CLLocation *driverLocations = [[CLLocation alloc]initWithLatitude:[[[[returnJson valueForKey:#"drivers"] objectAtIndex:x] valueForKey:#"driver_latitude"] doubleValue] longitude:[[[[detail valueForKey:#"drivers"] objectAtIndex:x] valueForKey:#"driver_longitude"] doubleValue]];
[driverPins addObject:driverLocations];
}
self.googleMapsDriverPins = [NSMutableArray arrayWithCapacity:driverPins.count];
for (CLLocation *newLocation in driverPins) {
GMSMarker *googleMapsDriverPin = [[GMSMarker alloc] init];
[googleMapsDriverPin setPosition:newLocation.coordinate];
[googleMapsDriverPin setAnimated:YES];
[googleMapsDriverPin setTitle:#"title"];
[googleMapsDriverPin setSnippet:#"snippet"];
[googleMapsDriverPin setIcon:[GMSMarker markerImageWithColor:[UIColor blackColor]]];
[googleMapsDriverPin setMap:googleMaps];
[self.googleMapsDriverPins addObject:googleMapsDriverPin];
}
}
#end
It looks like you have a loop adding multiple drivers, each of which assigns to the member variable googleMapsDriverPin. Then next time it removes the googleMapsDriverPin - but that will only be the last pin you added, not all of them.
For this to work you would need to add each marker inside your loop to an array, and then remove all of them from the map on your next update.
In Swift 2:
Create an outlet for your map:
#IBOutlet weak var mapView: GMSMapView!
Create an array to store all markers
var markers = [GMSMarker]()
Create markers like this:
func funcName() {
let position = CLLocationCoordinate2DMake(lat, lon)
let marker = GMSMarker(position: position)
for pin: GMSMarker in self.markers {
if pin.userData as! String == "from" {
pin.map = nil
}
}
marker.icon = UIImage(named: "navigation-red")
marker.userData = "from"
marker.map = self.mapView
self.markers.append(marker)
}
You can set the userData property to anything you want and later on use that string to delete that marker.When the funcName function is executed, all markers with userData as "from" are removed from the map.Let me know if you have any queries.

Dropping a MKPinAnnotationView with animation

I'm confused on how this works. I'm creating a CLGeocoder to drop a pin based on a string value. I have this:
- (void)placeMarkFromString:(NSString *)address {
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
[placemarks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(#"%#", [obj description]);
}];
// Check for returned placemarks
if (placemarks && [placemarks count] > 0) {
CLPlacemark *topResult = [placemarks objectAtIndex:0];
// Create an MKPlacemark and add it to the mapView
MKPlacemark *place = [[MKPlacemark alloc] initWithPlacemark:topResult];
AddressAnnotation *anAddress = [[AddressAnnotation alloc] init];
anAddress.address = place.subThoroughfare;
anAddress.street = place.thoroughfare;
anAddress.city = place.locality;
anAddress.state = place.administrativeArea;
anAddress.zip = place.postalCode;
anAddress.name = place.name;
//[self.mapView addAnnotation:place];
[self.mapView addAnnotation:anAddress];
self.currentPlacemark = place;
// Center map on that region
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(topResult.location.coordinate, 2000, 2000);
MKCoordinateRegion adjustedRegion = [_mapView regionThatFits:region];
[_mapView setRegion:adjustedRegion animated:YES];
}
else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"No Results Found" message:#"" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
}
if (error) {
NSLog(#"Error: %#", [error localizedDescription]);
}
}];
}
So originally, I added my MKPlacemark to the map and it shows the red pin. It does not animate however. I basically want the ability to drop any of the 3 MKPinAnnotationView colors, have a callout and the title/subtitle to be the name and address of the place, similar to how google maps does it. But I was not getting any animation.
So I thought that maybe I needed to create my own object that conforms to the MKAnnotation class. So I did that, but when I try to add it to the location, I do not see its annotationView in the viewForAnnotation delegate method. That method is here:
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id<MKAnnotation>)annotation {
static NSString *placeMarkIdentifier = #"SimplePinIdentifier";
if ([annotation isKindOfClass:[AddressAnnotation class]]) {
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[theMapView dequeueReusableAnnotationViewWithIdentifier:placeMarkIdentifier];
if (annotationView == nil) {
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:placeMarkIdentifier];
}
else {
annotationView.annotation = annotation;
}
annotationView.enabled = YES;
annotationView.animatesDrop = YES;
annotationView.draggable = YES;
annotationView.pinColor = MKPinAnnotationColorPurple;
annotationView.canShowCallout = YES;
// Create a button for the annotation
// UIButton *rightArrowButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
// annotationView.rightCalloutAccessoryView = rightArrowButton;
// [self performSelector:#selector(openCallout:) withObject:annotation afterDelay:0.5];
return annotationView;
}
return nil;
}
So I guess my questions are, do I need to create my own object to do this, am I on the right track, what am I doing wrong, and why is it that in one case, I am adding a MKPlacemark object, and then if I do it the way other way, I add a object, but not necessarily a subclass of MKPlacemark. Thanks!
First, the reason you're probably not seeing the annotation view when you use a custom annotation object (ie. AddressAnnotation) is that its coordinate is not being set (and so it's appearing at 0,0).
In the placeMarkFromString method, the code sets a lot of properties of the AddressAnnotation but not the coordinate and so the annotation is not appearing where expected.
If you set the coordinate, it will appear where expected and with the animation.
Regarding the other question as to why you see no animation when using an MKPlacemark:
By default, the map view will create an MKPinAnnotationView with a red pin but with animatesDrop set to NO.
So in viewForAnnotation, you have to explicitly do it for MKPlacemark just like you are for AddressAnnotation.
If all your annotations will be using MKPinAnnotationView, instead of checking for each different class of annotation you will be creating, you can flip the condition around by returning nil at the top of the method when the annotation class is MKUserLocation and then run the rest of the code without any class-checking (ie. returning MKPinAnnotationView with animatesDrop set to YES in all other cases).

Maximum number of annotations (MKAnnotation) that can be drawn on MKMapView?

I want to add a number of annotations(arround 500) on a mapview but the maximum it seems to display is 100. If I go beyond 100, viewForAnnotation delegate method is not called. However it works perfectly for annotations below 100.
here is the code: (works only when count of annotationArray array is less than 101)
_allPoints = [[NSMutableArray alloc] init];
NSString* responseFile = [[NSBundle mainBundle] pathForResource:#"Datafile" ofType:#"txt"];
NSData *sampleData = [NSData dataWithContentsOfFile:responseFile];
if (sampleData) {
NSError* error;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:sampleData
options:kNilOptions
error:&error];
NSArray* response = [json objectForKey:#"response"];
for (NSDictionary *lineDict in response) {
if ([[lineDict objectForKey:#"data"] isKindOfClass:[NSArray class]]) {
SinglePoint *singlePoint = [[SinglePoint alloc] initWithDictionary:lineDict];
[_allPoints addObject:singlePoint];
}
else {
NSLog(#"Error");
}
}
}
_mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[_mapView setDelegate:self];
[self.view addSubview:_mapView];
NSMutableArray *annotationArray = [[NSMutableArray alloc] init];
for (int i=0; i<[_allPoints count]; i++) {
SinglePoint *singlePoint = [_allPoints objectAtIndex:i];
NVPolylineAnnotation *annotation = [[NVPolylineAnnotation alloc] initWithPoint:singlePoint mapView:_mapView];
[annotationArray addObject:annotation];
}
[_mapView addAnnotations:(NSArray *)annotationArray];
CLLocationCoordinate2D centerCoord = { 28.632747, 77.219982 };
[_mapView setCenterCoordinate:centerCoord zoomLevel:12 animated:NO];
The delegate method is:
EDIT: As per comments, started reusing the view but with no luck :(
if ([annotation isKindOfClass:[NVPolylineAnnotation class]]) {
static NSString *viewIdentifier = #"annotationView";
NVPolylineAnnotationView *annotationView = (NVPolylineAnnotationView *) [mapView dequeueReusableAnnotationViewWithIdentifier:viewIdentifier];
if (annotationView == nil) {
annotationView = [[NVPolylineAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:viewIdentifier mapView:_mapView];
}
return annotationView;
}
return nil;
I could not find any restriction in the documentation or anywhere else. Could it be memory issue?
The MKMapView has not Limit for annotationViews. But it gets quite laggy and unusable above a certain number of views (+1000).
I believe, that the reason for this is that you handle the annotationView management totally wrong. You shouldn't create a unique view for every single annotation, even if you are using ARC. Your rather should reuse every unused View like the cell of a UITableView.
There are a couple of good tutorials for this like Introduction to MapKit on iOS - Ray Wenderlich.
If this won't resolve your problem, you should try to debug your annotation classes. (NVPolylineAnnotation and NVPolylineAnnotationView). Maybe there's something wrong.
Did you also checked your points array for equal coordinates?
Finally was able to track down the problem. Solved it by setting the center of mapView first and then adding annotations later. I still dont know why 100 annotations were shown earlier (and why the no 100 only). Thank you all for your suggestions and time on this.
This is the code I changed:-
_mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[_mapView setDelegate:self];
[self.view addSubview:_mapView];
CLLocationCoordinate2D centerCoord = { 28.632747, 77.219982 };
[_mapView setCenterCoordinate:centerCoord zoomLevel:12 animated:NO];
NSMutableArray *annotationArray = [[NSMutableArray alloc] init];
for (int i=0; i<[_allPoints count]; i++) {
SinglePoint *singlePoint = [_allPoints objectAtIndex:i];
NVPolylineAnnotation *annotation = [[NVPolylineAnnotation alloc] initWithPoint:singlePoint mapView:_mapView];
[annotationArray addObject:annotation];
}
[_mapView addAnnotations:(NSArray *)annotationArray];
You can add as many annotations as you like. However, the views for each annotation will not be created until that annotation's coordinate property intersects with the visible portion of your map view. MapKit will not create a view just because you added an annotation to the map.

Problem Refreshing iPhone MapView

Hey guys, I am having trouble getting overlays in my map view to refresh via the setNeedsDisplayInMapRect: function. Here is the relevant code:
ParkingMapViewController.m:
for (ParkingRegionOverlay *overlay in mapView.overlays) {
[overlay setNeedsDisplayInMapRect:self.mapView.visibleMapRect];
}
//...
- (MKOverlayView *)mapView:(MKMapView *)mapView
viewForOverlay:(id <MKOverlay>)overlay
{
NSLog(#"ParkingMapViewController.m mapView:viewForOverlay");
//...
}
//...
ParkingRegionOverlay.h:
#interface ParkingRegionOverlay : MKOverlayView <MKOverlay> {
MKPolygon *polygon;
MKMapRect boundingRect;
CLLocationCoordinate2D centerCoord;
//...
}
//...
And I am not getting the "ParkingMapViewController.m mapView:viewForOverlay" output to console I am expecting. I have walked through he debugger and have ensured that the for loop is being reached and executed, however mapView:viewForOverlay: isn't being called for some reason. Anyone know what I am doing wrong? Thanks in advance!
EDIT 1:
I believe I have set the delegate, coordinates, and bounding rect properly, but please take a look...
ParkingMapViewController.h
#interface ParkingMapViewController : UIViewController <MKMapViewDelegate> {
MKMapView *mapView;
//...
ParkingMapViewController.m:
//...
- (void)viewDidLoad {
[super viewDidLoad];
mapView.delegate = self;
//...
ParkingRegionOverlay.m:
//...
//initializes polygon and calculates bounding rect as well as its center coordinate
-(id)initWithPoints:(NSArray *)pointsArray andTitle:(NSString *)overlayTitle{
MKMapPoint points[[pointsArray count]];
double maxX = MIN_COORD_VAL;
double minX = MAX_COORD_VAL;
double maxY = MIN_COORD_VAL;
double minY = MAX_COORD_VAL;
double tempX = 0;
double tempY = 0;
if (self = [super init]) {
int i = 0;
//determine min/max extrema to help calculate the bounding rect
for (id coordDict in pointsArray){
tempX = [[coordDict objectForKey:#"latitude"] doubleValue];
tempY = [[coordDict objectForKey:#"longitude"] doubleValue];
maxX = fmax(tempX, maxX);
minX = fmin(tempX, minX);
maxY = fmax(tempY, maxY);
minY = fmin(tempY, minY);
CLLocationCoordinate2D coord = {tempX,tempY};
points[i] = MKMapPointForCoordinate(coord);
i++;
}//for
CLLocationCoordinate2D northWestCorner = CLLocationCoordinate2DMake(maxX, minY);
CLLocationCoordinate2D southEastCorner = CLLocationCoordinate2DMake(minX, maxY);
MKMapPoint northWestPoint = MKMapPointForCoordinate(northWestCorner);
MKMapPoint southEastPoint = MKMapPointForCoordinate(southEastCorner);
boundingRect = MKMapRectMake(northWestPoint.x, northWestPoint.y,
(southEastPoint.x-northWestPoint.x),
(southEastPoint.y-northWestPoint.y));
centerCoord = CLLocationCoordinate2DMake((maxX-minX)/2,(maxY-minY)/2);
polygon = [MKPolygon polygonWithPoints:points count:[pointsArray count]];
polygon.title = overlayTitle;
[self initAcceptedPermitsBasedOnTitle:overlayTitle];
}//if
return self;
}
//...
Thanks.
EDIT 2:
An alternate method I have tried, to no avail:
ParkingMapViewController.m
NSArray *overlayArray = [[NSArray alloc] initWithArray:[mapView overlays]];
[self.mapView removeOverlays:mapView.overlays];
[self.mapView addOverlays:overlayArray];
Removing and re-adding all overlays ain't working too well for me. It merely crashes when that third line is executed. Any ideas?
EDIT 3:
So I changed the previously posted code to the following:
NSArray *overlayArray = [mapView overlays];
[self.mapView removeOverlays:overlayArray];
[self.mapView addOverlays:overlayArray];
And am now seeing this in the console:
2011-05-05 14:24:54.145 Parking[68501:207] -[NSCFNumber boundingMapRect]: unrecognized selector sent to instance 0xa9afae0
2011-05-05 14:24:54.147 Parking[68501:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSCFNumber boundingMapRect]: unrecognized selector sent to instance 0xa9afae0'
Chances are you haven't set up the coordinate or boundingMapRect properties on the MKOverlay correctly. The MapView will only ask for the view if it thinks there is a possibility that it is visible, if its visible rect doesn't intersect the boundMapRect, it won't.
Also make sure your delegate for the mapView is set properly.
So I figured it out. Not necessarily the most efficient method, but it works for me. This is what I did:
[self.mapView removeOverlays:[mapView overlays]];
[self loadOverlaysAndAnnotations];
And here is loadOverlaysAndAnnotations:
- (void)loadOverlaysAndAnnotations {
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
CoreDataSingleton *coreDataSingleton = [CoreDataSingleton sharedManager];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"ParkingLot" inManagedObjectContext:[coreDataSingleton managedObjectContext]];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [[coreDataSingleton managedObjectContext] executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *overlayEntity in fetchedObjects) {
NSArray *pointsArray = [NSArray arrayWithArray:[overlayEntity valueForKey:#"overlayCoordPoints"]];
ParkingRegionOverlay *regionPolygon = [[ParkingRegionOverlay alloc] initWithPoints:pointsArray andTitle:[overlayEntity valueForKey:#"lotName"]];
[mapView addOverlay:regionPolygon];
[regionPolygon release];
NSSet *annotationsSet = [NSSet setWithSet:[overlayEntity valueForKey:#"parkingAnnotations"]];
NSArray *allAnnotations = [NSArray arrayWithArray:[annotationsSet allObjects]];
CLLocationCoordinate2D workingCoordinate;
for (ParkingAnnotations *annotation in allAnnotations) {
ParkingAnnotation *parkingAnnot = [[ParkingAnnotation alloc] init];
workingCoordinate.latitude = [[annotation latitude] doubleValue];
workingCoordinate.longitude = [[annotation longitude] doubleValue];
[parkingAnnot setCoordinate:workingCoordinate];
[parkingAnnot setTitle:[overlayEntity valueForKey:#"lotName"]];
if ([[overlayEntity valueForKey:#"lotName"] isEqualToString:#"VP 1"]) {
[parkingAnnot setLot:lot1];
}
[mapView addAnnotation:parkingAnnot];
[parkingAnnot release];
}
}
[fetchRequest release];
}//loadOverlaysAndAnnotations
In short, I didn't have to create a new function but merely call the function I used to load overlays into the map view and that works fine! Hope this helps anyone else stuck in a similar situation.
EDIT:
Important to note that I am reloading BOTH annotations and overlays, and, if done without first removing both annotations and overlays, can lead to crashing of your app if the reload function is called too many times. This is what I am currently experiencing. Just something to be aware of. To fix this I am going to have separate load functions, one for overlays, and one for annotations which will be called appropriately.

Load only five annotations to MKMapVIew

I have a MKMapView, and I would like to know how I can find the nearest 5 annotations to the user, and only display them on the MKMapView.
My code currently is:
- (void)loadFiveAnnotations {
NSString *string = [[NSString alloc] initWithContentsOfURL:url];
string = [string stringByReplacingOccurrencesOfString:#"\n" withString:#""];
NSArray *chunks = [string componentsSeparatedByString:#";"];
NSArray *keys = [NSArray arrayWithObjects:#"type", #"name", #"street", #"address1", #"address2", #"town", #"county", #"postcode", #"number", #"coffeeclub", #"latlong", nil];
// max should be a multiple of 12 (number of elements in keys array)
NSUInteger max = [chunks count] - ([chunks count] % [keys count]);
NSUInteger i = 0;
while (i < max)
{
NSArray *subarray = [chunks subarrayWithRange:NSMakeRange(i, [keys count])];
NSDictionary *dict = [[NSDictionary alloc] initWithObjects:subarray forKeys:keys];
// do something with dict
NSArray *latlong = [[dict objectForKey:#"latlong"] componentsSeparatedByString:#","];
NSString *latitude = [[latlong objectAtIndex:0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *longitude = [[latlong objectAtIndex:1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
CLLocationDegrees lat = [latitude floatValue];
CLLocationDegrees longi = [longitude floatValue];
Annotation *annotation = [[Annotation alloc] initWithCoordinate:CLLocationCoordinate2DMake(lat, longi)];
annotation.title = [dict objectForKey:#"name"];
annotation.subtitle = [NSString stringWithFormat:#"%#, %#, %#",[dict objectForKey:#"street"],[dict objectForKey:#"county"], [dict objectForKey:#"postcode"]];
[mapView addAnnotation:annotation];
[dict release];
i += [keys count];
}
}
A long answer, already mostly written when Stephen Poletto posted and containing example code on how to use the built-in methods for sorting an array, so I though it was still worth posting though the essential answer is the same (ie, "pick the five closest for yourself, pass only those on"):
You're going to need to sort your annotations by distance for yourself, and submit only the closest five to the MKMapView. If you have two CLLocations then you can get the distance between them using the distanceFromLocation: method (which was getDistanceFrom: prior to iOS 3.2; that name is now deprecated).
So, for example, supposing your Annotation class had a method 'setReferenceLocation:' to which you pass a CLLocation and a getter 'distanceFromReferenceLocation' which returns the distance between the two, you could do:
// create and populate an array containing all potential annotations
NSMutableArray *allPotentialAnnotations = [NSMutableArray array];
for(all potential annotations)
{
Annotation *annotation = [[Annotation alloc]
initWithCoordinate:...whatever...];
[allPotentialAnnotations addObject:annotation];
[annotation release];
}
// set the user's current location as the reference location
[allPotentialAnnotations
makeObjectsPerformSelector:#selector(setReferenceLocation:)
withObject:mapView.userLocation.location];
// sort the array based on distance from the reference location, by
// utilising the getter for 'distanceFromReferenceLocation' defined
// on each annotation (note that the factory method on NSSortDescriptor
// was introduced in iOS 4.0; use an explicit alloc, init, autorelease
// if you're aiming earlier)
NSSortDescriptor *sortDescriptor =
[NSSortDescriptor
sortDescriptorWithKey:#"distanceFromReferenceLocation"
ascending:YES];
[allPotentialAnnotations sortUsingDescriptors:
[NSArray arrayWithObject:sortDescriptor]];
// remove extra annotations if there are more than five
if([allPotentialAnnotations count] > 5)
{
[allPotentialAnnotations
removeObjectsInRange:NSMakeRange(5,
[allPotentialAnnotations count] - 5)];
}
// and, finally, pass on to the MKMapView
[mapView addAnnotations:allPotentialAnnotations];
Depending on where you're loading from, you made need to create a local store (in memory or on disk) for annotations and select the five nearest whenever the user moves. Either register yourself as a CLLocationManager delegate or key-value observe on the map view's userLocation property. If you have quite a lot of potential annotations then sorting all of them is a bit wasteful and you'd be better advised to use a quadtree or a kd-tree.
First you'll need to grab the user's current location. You can build a CLLocationManager and register yourself as the delegate for location updates as follows:
locationManager = [[[CLLocationManager alloc] init] autorelease];
[locationManager setDelegate:self];
[locationManager startUpdatingLocation];
After setting yourself as the delegate, you'll receive the following callback:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
Now that you have the user's location (newLocation), you can find the five closest annotations. There is a handy method in CoreLocation:
- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location
As you're iterating through your annotations, just store the five nearest locations. You can build a CLLocation out of the 'lat' and 'longi' variables you have using:
- (id)initWithLatitude:(CLLocationDegrees)latitude longitude:(CLLocationDegrees)longitude
Hope this helps!