Optimizing Code for MKMapView - Large Number of Annotations - iphone

I have a modal view in my app which displays a UIMapView. I then add a large number of annotations (over 800) to this map view (code below).
The problem is that the user is forced to wait a minute or so while all the pins load. Also the app becomes sluggish once all 800 pins are on the map.
Can anyone suggest how I can improve my code below ?
Thank you.
#import "MapView.h"
#import "MapPlaceObject.h"
#implementation MapView
#synthesize mapViewLink, mapLocations, detail, failedLoad;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
-(void)addPins
{
for (MapPlaceObject * info in mapLocations) {
double latitude = info.longitude;
double longitude = info.latitude;
NSString * name = info.name;
NSString * addressline = info.addressOne;
NSString * postcode = info.postCode;
NSString * addresscomma = [addressline stringByAppendingString:#", "];
NSString * address = [addresscomma stringByAppendingString:postcode];
CLLocationCoordinate2D coordinate;
coordinate.latitude = latitude;
coordinate.longitude = longitude;
MyLocation *annotation = [[[MyLocation alloc] initWithName:name address:address coordinate:coordinate] autorelease];
[mapViewLink addAnnotation:annotation];
}
}
- (void)showLinks : (id)sender {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
detail = [[DetailViewController alloc] initWithNibName:#"DetailViewController-iPad" bundle:nil];
}
else if (!detail) {
NSLog(#"Detail is None");
detail = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
}
int uniqueID = ((UIButton *)sender).tag;
//PlaceObject *info = [mapLocations objectAtIndex:uniqueID];
detail.UniqueID = uniqueID;
detail.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:detail animated:YES];
self.detail = nil;
[detail release];
}
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation{
if (annotation == mapView.userLocation){
return nil; //default to blue dot
}
MKPinAnnotationView *annView=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"currentloc"];
annView.pinColor = MKPinAnnotationColorRed;
nameSaved = annotation.title;
for (PlaceObject * info in mapLocations) {
if (info.name == nameSaved) {
saveID = info.UniqueID;
}
}
UIButton *advertButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
advertButton.frame = CGRectMake(0, 0, 23, 23);
advertButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
advertButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
[advertButton addTarget:self action:#selector(showLinks:) forControlEvents:UIControlEventTouchUpInside];
advertButton.tag = saveID;
annView.rightCalloutAccessoryView = advertButton;
annView.animatesDrop=TRUE;
annView.canShowCallout = YES;
annView.calloutOffset = CGPointMake(-5, 5);
return annView;
}
- (void)dealloc
{
[mapViewLink release];
[mapLocations release];
[detail release];
self.failedLoad = nil;
[failedLoad release];
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewWillAppear:(BOOL)animated {
if (firstTime) {
CLLocationCoordinate2D zoomLocation;
zoomLocation.latitude = 51.50801;
zoomLocation.longitude = -0.12789;
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, 15*METERS_PER_MILE, 15*METERS_PER_MILE);
MKCoordinateRegion adjustedRegion = [mapViewLink regionThatFits:viewRegion];
[mapViewLink setRegion:adjustedRegion animated:YES];
firstTime = NO;
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
firstTime = YES;
failedLoad = [[NSMutableArray alloc]init];
self.mapLocations = [BluePlaqueDatabase database].mapInfo;
[self addPins];
}
- (void)viewDidUnload
{
[mapViewLink release];
mapViewLink = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}

The two biggest speed improvements you can make here are:
Implement annotation view re-use (right now it creates a new view every time it needs to show an annotation even if the same one comes into view again.
Change how the UniqueID is set. To set it, the code is currently looping through all the annotations every time it creates an annotation view (which could happen any time the map view is zoomed or scrolled--not just the initial time).
First, instead of searching for the UniqueID in the viewForAnnotation method and using a button tag to pass the annotation identifier, add UniqueID as a property to your custom annotation class MyLocation and set the property when you add the annotation itself in addPins:
annotation.uniqueID = info.UniqueID; // <-- give id to annotation itself
[mapViewLink addAnnotation:annotation];
You could also add uniqueID as a parameter to the initWithName method instead of assigning the property separately.
Next, to implement annotation view re-use, the viewForAnnotation method should look like this:
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation{
if (annotation == mapView.userLocation){
return nil; //default to blue dot
}
NSString *reuseId = #"StandardPin";
MKPinAnnotationView *annView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (annView == nil)
{
annView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId] autorelease];
annView.pinColor = MKPinAnnotationColorRed;
annView.animatesDrop = YES;
annView.canShowCallout = YES;
annView.calloutOffset = CGPointMake(-5, 5);
UIButton *advertButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
advertButton.frame = CGRectMake(0, 0, 23, 23);
advertButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
advertButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
annView.rightCalloutAccessoryView = advertButton;
}
else
{
//update the annotation property if view is being re-used...
annView.annotation = annotation;
}
return annView;
}
Finally, to respond to the button press and figure out which UniqueID to show the detail for, implement the calloutAccessoryControlTapped delegate method:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control
{
MyLocation *myLoc = (MyLocation *)view.annotation;
int uniqueID = myLoc.uniqueID;
NSLog(#"calloutAccessoryControlTapped, uid = %d", uniqueID);
//create, init, and show the detail view controller here...
}
After all these changes, only the initial loading of the annotations will take up most of the time. If that is still a problem, one solution is to only add annotations that would be visible in the currently displayed region and add/remove annotations as the user changes the visible region.

I completely agree with Anna. But consider that 800 AnnotationViews at the same time will result in a extreme laggy interface. So if your map should provide user interaction like scrolling or zooming you better implement some kind clustering of your annotation views.

Related

Detail Disclosure Indicator MKMap to Custom View Controller

EXTRA CODE ADDED
I am new to xcode. I have managed to create annotations on my map in various different colours. What I would like to do is have each annotation lead to somewhere new with a disclosure button. I have implemented a right arrow disclosure button now what I need to know is how I link that up so that each annotation will lead to a different view controller of my choice. Here is my current code on my MapViewController implementation.
MainMapViewController.m
#import "MainMapViewController.h"
#import "LocationAnnotation.h"
#interface MainMapViewController ()
#end
//Totnes Main Centre Coordinates
#define Totnes_LATITUDE 50.433741
#define Totnes_LONGITUDE -3.685797
//The Dartmouth Inn Coordinates
#define DARTMOUTH_INN_LATITUDE 50.430036;
#define DARTMOUTH_INN_LONGITUDE -3.683873;
//Pub Offers Co-Ordinates
#define TheKingBill_LATITUDE 50.431379
#define TheKingBill_LONGITUDE -3.685495
#define TheSevenStars_LATITUDE 50.431045
#define TheSevenStars_LONGITUDE -3.682945
#define TheLordNelson_LATITUDE 50.430931
#define TheLordNelson_LONGITUDE -3.683644
//Span
#define THE_SPAN 0.01f;
#implementation MainMapViewController
#synthesize mainMapView;
- (void)viewDidLoad
{
[super viewDidLoad];
//Set Delegate
mainMapView.delegate = self;
//Create the region
MKCoordinateRegion myRegion;
//Centre
CLLocationCoordinate2D centre;
centre.latitude = Totnes_LATITUDE;
centre.longitude = Totnes_LONGITUDE;
//Span
MKCoordinateSpan span;
span.latitudeDelta = THE_SPAN;
span.longitudeDelta = THE_SPAN;
myRegion.center = centre;
myRegion.span = span;
//Set The Map View
[mainMapView setRegion:myRegion animated:YES];
//Annotation
NSMutableArray * locations = [[NSMutableArray alloc] init];
LocationAnnotation * myAnn;
//The King Bill Annotation
myAnn = [[LocationAnnotation alloc] initWithTitle:#"The King Bill"
andSubtitle:#"Another Pub In Town"
andCoordinate:CLLocationCoordinate2DMake(TheKingBill_LATITUDE, TheKingBill_LONGITUDE)
andID:1];
[locations addObject:myAnn];
//The Seven Stars Annotations
myAnn = [[LocationAnnotation alloc] initWithTitle:#"The Royal Seven Stars Hotel"
andSubtitle:#"Hotel In Town"
andCoordinate:CLLocationCoordinate2DMake(TheSevenStars_LATITUDE, TheSevenStars_LONGITUDE)
andID:2];
[locations addObject:myAnn];
//The Lord Nelson Annotations
myAnn = [[LocationAnnotation alloc] initWithTitle:#"The Lord Nelson"
andSubtitle:#"Great Pub In Centre of Town"
andCoordinate:CLLocationCoordinate2DMake(TheLordNelson_LATITUDE, TheLordNelson_LONGITUDE)
andID:3];
[locations addObject:myAnn];
[self.mainMapView addAnnotations:locations];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - MKMapViewDelegate
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation {
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
MKPinAnnotationView *annView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"pin"];
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annView.rightCalloutAccessoryView = rightButton;
int annId = ((LocationAnnotation *)annotation).idNumber;
annView.pinColor = (annId == 1) ? MKPinAnnotationColorPurple
: (annId == 2) ? MKPinAnnotationColorGreen
: MKPinAnnotationColorRed;
annView.canShowCallout = YES;
return annView;
}
#end
set rightbutton's tag to annID.
rightButton.tag = annId;
and then assign Selector to touchup event:
[rightButton addTarget:self action:#selector(YourMethod:) forControlEvents:UIControlEventTouchUpInside];
In YourMethod, you can use senders tag for navigating to different view
-(void)YourMethod:(UIButton*)sender
{
if(sender.tag==1)
{
//push viewcontroller1
}
else
{
//push viewcontroller
}
return;
}
As you can see, your right button is of type UIButton. Because of this fact you can addTarget to it:
[rightButton addTarget:self action:#selector(YourMethod) forControlEvents:UIControlEventTouchUpInside];
After user tap on rightButton YourMethod will be launched.
So for every pin you can add different button with different method, in the same way you are setting AnnotationColor
int annId = ((LocationAnnotation *)annotation).idNumber;
if (annId == 1)
{
annView.pinColor = MKPinAnnotationColorPurple;
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annView.rightCalloutAccessoryView = rightButton;
[rightButton addTarget:self action:#selector(MethodWhereYouWillFireYouNewViewController) forControlEvents:UIControlEventTouchUpInside];
}
else if (annId == 2)
{
//similar as above
}
Check This:
-(void)clickOnMapAnnotation:(UIButton*)sender
{
int AnnotationClicked =sender.tag;
if(AnnotationClicked ==1)
{
//PushViewcontroller1
}
else
{
//PushViewcontroller1
}
}
(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id ) annotation {
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
MKPinAnnotationView *annView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"pin"];
int annId = ((LocationAnnotation *)annotation).idNumber;
annView.pinColor = (annId == 1) ? MKPinAnnotationColorPurple
: (annId == 2) ? MKPinAnnotationColorGreen
: MKPinAnnotationColorRed;
//The Detail Disclosure button that I want to lead to a new view controller.
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self action:#selector(clickOnMapAnnotation:) forControlEvents:UIControlEventTouchUpInside];
rightButton.tag = annId;
annView.rightCalloutAccessoryView = rightButton;
annView.canShowCallout = YES;
return annView;
}

How to make the callout button in an annotation in MKMapKit go to another view controller

I want the button in the callout to go to another view controller to display extra information, and then be able to come back to the map view. the map is inside of a tabbed view controller. At the moment, if you click on the button in the callout, the app crashes and gives you a thread error. Not sure on what to do at the moment.
this is the header file:
#import UIKit/UIKit.h>
#import MapKit/MapKit.h>
#define METERS_PER_MILE 1609.344
#interface MSKSecondViewController :UIViewController<MKMapViewDelegate>
{
IBOutlet MKMapView *stillwellMapView;
}
#property (nonatomic, assign) CLLocationCoordinate2D mainEntranceToStillwellCoordinate;
#end
and this is the implementation file:
#interface MSKSecondViewController ()
#end
#implementation MSKSecondViewController
#synthesize mainEntranceToStillwellCoordinate;
- (void)viewDidLoad
{
[super viewDidLoad];
stillwellMapView.delegate=self;
// Do any additional setup after loading the view, typically from a nib.
[self mainEntrancetoStillwellCoordinate];
[self bambooForestCoordinate];
}
- (void)mainEntrancetoStillwellCoordinate
{
MKPointAnnotation * main = [[MKPointAnnotation alloc]init];
CLLocationCoordinate2D mainLocation;
mainLocation.latitude = 40.831685;
mainLocation.longitude = -73.477453;
[main setCoordinate:mainLocation];
[main setTitle:#"Entrance"];
[main setSubtitle:#"Main"];
[stillwellMapView addAnnotation:main];
}
- (void)bambooForestCoordinate
{
MKPointAnnotation * bambooForest = [[MKPointAnnotation alloc]init];
CLLocationCoordinate2D bambooForestLocation;
bambooForestLocation.latitude = 40.829118;
bambooForestLocation.longitude = -73.466443;
[bambooForest setCoordinate:bambooForestLocation];
[bambooForest setTitle:#"Bamboo Forest"];
[bambooForest setSubtitle:#"Exit to Woodbury"];
[stillwellMapView addAnnotation:bambooForest];
}
- (void)viewDidUnload
{
stillwellMapView = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
}
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:
(id<MKAnnotation>)annotation
{
MKPinAnnotationView * annView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:nil];
UIButton *entranceButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[entranceButton addTarget:self action:#selector(entranceButtonPressed:)
forControlEvents:UIControlEventTouchUpInside];
annView.rightCalloutAccessoryView = entranceButton;
entranceButton = [UIButton buttonWithType:UIButtonTypeCustom];
entranceButton.frame = CGRectMake(0, 0, 23, 23);
entranceButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
entranceButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
[UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annView.animatesDrop=TRUE;
annView.canShowCallout = YES;
annView.calloutOffset = CGPointMake(-5, 5);
return annView;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (void)viewWillAppear:(BOOL)animated
{
//the coordinates in which the map shows once loaded
CLLocationCoordinate2D zoomLocation;
zoomLocation.latitude = 40.831922;
zoomLocation.longitude= -73.476353;
//the amount of area shown by the map when it loads
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation,
0.15*METERS_PER_MILE, 0.15*METERS_PER_MILE);
MKCoordinateRegion adjustedRegion = [stillwellMapView regionThatFits:viewRegion];
[stillwellMapView setRegion:adjustedRegion animated:YES];
}
#end
Add the event which you have registered on Touch Up Inside with entranceButton.
-(void) entranceButtonPressed:(UIButton *)sender
{
//Write the controller push code here
}
Also, in the code you have reinitialize the entranceButton once it is assigned to rightCalloutAccessoryView.
In the mapView:viewForAnnotation: delegate method
-(MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation
// add the following line
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
pinView.rightCalloutAccessoryView = rightButton;
and then implement the code to navigate to other view controller in the following delegate method
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
Make sure view controller is embedded inside navigation controller

MKMapView automatically zoom in to current location when I zoom out

I've created MKMapView and used UIBarButtonItem for zoom out to show all regions. It worked fine on iPhone Simulator but when I try on device it was zoom back to current location after 3-5 seconds or sometimes 10. I don't know what something wrong here. Thanks for any advice.
This following is my code
MapViewController.h
#import <UIKit/UIKit.h>
#import "MapListViewController.h"
#class MCLocation;
#interface MapViewController : UIViewController <MKMapViewDelegate, CLLocationManagerDelegate, MapListViewControllerDelegate>
{
CLLocationManager *locationManager;
NSArray *locations;
__weak IBOutlet MKMapView *worldView;
__weak IBOutlet UISegmentedControl *mapTypeControl;
}
#property (nonatomic, strong) MCLocation *item;
#property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
- (IBAction)changeMapType:(id)sender;
- (void)zoomLocation:(CLLocationCoordinate2D)i;
#end
MapViewController.m
#import "MapViewController.h"
#import "MCLocation.h"
#import "MCLocationStore.h"
#import "MapDetailViewController.h"
#define METERS_PER_MILE 1609.344
#interface MapViewController ()
#end
#implementation MapViewController
#synthesize item;
#synthesize fetchedResultsController;
#synthesize managedObjectContext;
- (id)init
{
self = [super initWithNibName:#"MapViewController" bundle:nil];
if (self) {
[[self navigationItem] setTitle:#"Map"];
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStyleBordered target:self action:nil];
[[self navigationItem] setBackBarButtonItem:backButton];
UIImage *userImage = [UIImage imageNamed:#"User.png"];
UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] initWithImage:userImage style:UIBarButtonItemStyleBordered target:self action:#selector(showUser)];
UIImage *locationImage = [UIImage imageNamed:#"Pin.png"];
UIBarButtonItem *rightButton = [[UIBarButtonItem alloc] initWithImage:locationImage style:UIBarButtonItemStyleBordered target:self action:#selector(showLocation)];
[[self navigationItem] setLeftBarButtonItems:leftButton];
[[self navigationItem] setRightBarButtonItems:rightButton];
}
return self;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
return [self init];
}
- (MKCoordinateRegion)regionForAnnotations:(NSArray *)annotations
{
MKCoordinateRegion region;
if ([annotations count] == 0) {
region = MKCoordinateRegionMakeWithDistance(worldView.userLocation.coordinate, 1000, 1000);
} else if ([annotations count] == 1) {
id <MKAnnotation> annotation = [annotations lastObject];
region = MKCoordinateRegionMakeWithDistance(annotation.coordinate, 1000, 1000);
} else {
CLLocationCoordinate2D topLeftCoord;
topLeftCoord.latitude = -90;
topLeftCoord.longitude = 180;
CLLocationCoordinate2D bottomRightCoord;
bottomRightCoord.latitude = 90;
bottomRightCoord.longitude = -180;
for (id <MKAnnotation> annotation in annotations)
{
topLeftCoord.latitude = fmax(topLeftCoord.latitude, annotation.coordinate.latitude);
topLeftCoord.longitude = fmin(topLeftCoord.longitude, annotation.coordinate.longitude);
bottomRightCoord.latitude = fmin(bottomRightCoord.latitude, annotation.coordinate.latitude);
bottomRightCoord.longitude = fmax(bottomRightCoord.longitude, annotation.coordinate.longitude);
}
MKCoordinateSpan span;
span.latitudeDelta = 2.0;
span.longitudeDelta = 0.5;
region.span = span;
region.center = worldView.userLocation.coordinate;
return [worldView regionThatFits:region];
}
- (IBAction)showUser
{
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(worldView.userLocation.coordinate, 250, 250);
[worldView setRegion:[worldView regionThatFits:region] animated:YES];
}
- (IBAction)showLocation
{
MKCoordinateRegion region = [self regionForAnnotations:locations];
[worldView setRegion:region animated:YES];
}
- (void)updateLocations
{
if (locations != nil) {
[worldView removeAnnotations:locations];
}
locations = [self.fetchedResultsController fetchedObjects];
[worldView addAnnotations:locations];
}
- (void)zoomLocation:(CLLocationCoordinate2D)i
{
CLLocationCoordinate2D zoomLocation = i;
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, 0.5*METERS_PER_MILE, 0.5*METERS_PER_MILE);
MKCoordinateRegion adjustedRegion = [worldView regionThatFits:viewRegion];
[worldView setRegion:adjustedRegion animated:YES];
}
- (void)performFetch
{
NSError *error;
if (![self.fetchedResultsController performFetch:&error]) {
FATAL_CORE_DATA_ERROR(error);
return;
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[worldView setShowsUserLocation:YES];
[self performFetch];
[self loadMapTypePref];
[self updateLocations];
// If we have locations, then show them on the map. If there are no
// locations, then let the map view figure out how to center on the
// user's position. It will usually do a pretty good job.
if ([locations count] > 0) {
[self showLocation];
}
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
[mapTypeControl setHidden:YES];
CLLocationCoordinate2D coord = [item coordinate];
[worldView setCenterCoordinate:coord animated:NO];
[self zoomLocation:coord];
}
}
- (void)viewDidUnload
{
worldView = nil;
mapTypeControl = nil;
locations = nil;
fetchedResultsController = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc
{
[locationManager setDelegate:nil];
}
#pragma mark - MKMapViewDelegate
- (void)mapView:(MKMapView *)mapView
didUpdateUserLocation:(MKUserLocation *)userLocation
{
CLLocationCoordinate2D loc = [userLocation coordinate];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(loc, 250, 250);
[worldView setRegion:region animated:YES];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id<MKAnnotation>)annotation
{
static NSString *LocationIdentifier = #"Location";
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[worldView dequeueReusableAnnotationViewWithIdentifier:LocationIdentifier];
if ([annotation isKindOfClass:[MCLocation class]]) {
if (!annotationView) {
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:LocationIdentifier];
}
[annotationView setAnnotation:annotation];
[annotationView setPinColor:MKPinAnnotationColorRed];
[annotationView setEnabled:YES];
[annotationView setAnimatesDrop:YES];
[annotationView setCanShowCallout:YES];
[annotationView setCalloutOffset:CGPointMake(-5, 5)];
[annotationView setUserInteractionEnabled:YES];
UIButton *rightButton = nil;
UIImage *logoImage = [UIImage imageNamed:#"monkcup-map-pin.png.png"];
UIImageView *leftButton = [[UIImageView alloc] initWithImage:logoImage];
rightButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
[annotationView setRightCalloutAccessoryView:rightButton];
[annotationView setLeftCalloutAccessoryView:leftButton];
return annotationView;
} else {
[[worldView userLocation] setTitle:#"You're here"];
}
return nil;
}
- (void)mapView:(MKMapView *)mapView
annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control
{
// Call out code
}
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSTimeInterval t = [[newLocation timestamp] timeIntervalSinceNow];
if (t < -180) {
return;
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
//
}
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController) {
return fetchedResultsController;
}
// Create and configure a fetch request with the Book entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
MCLocationStore *ls = [MCLocationStore sharedStore];
self.managedObjectContext = ls.context;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MCLocation" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Create the sort descriptors array.
NSSortDescriptor *sd = [NSSortDescriptor sortDescriptorWithKey:#"storeName" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sd, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Create and initialize the fetch results controller.
fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"MapView"];
return fetchedResultsController;
}
#end
You've implemented the map view's didUpdateUserLocation delegate method and in there you are zooming the map to the user's location.
That delegate method will get called whenever the device gets a location update.
So after you zoom or pan away, if there's another location update, the delegate method gets called and it zooms back to the user location.
One option is to keep a boolean ivar (say didZoomToUserLocation) and in that method, you should only zoom (ie. call setRegion) if the flag is NO. Set the flag to YES in that method after calling setRegion.
In iOS 5 and up, you could also try setting the userTrackingMode to MKUserTrackingModeFollow instead of manually zooming. I think that mode gives the user some freedom to pan around while still following the user.

help with multiple images for mapkit annotations

Very new to xcode and somewhat confused. I was able to use a custom image for annotation - works fine. Problem is that I want to do is have a different images for each of the annotations. What should I add/change to the code below? Thanks in advance and remember, I'm a newbee!
#import "MapViewController.h"
#interface AddressAnnotation : NSObject<MKAnnotation> {
CLLocationCoordinate2D coordinate;
NSString *mTitle;
NSString *mSubTitle;
UIImage *image;
}
#property (nonatomic, retain) UIImage *image;
#end
#implementation AddressAnnotation
#synthesize coordinate;
#synthesize image;
- (NSString *)subtitle{
return mSubTitle;
}
- (NSString *)title{
return mTitle;
}
-(id)initWithCoordinate:(CLLocationCoordinate2D) c Title: (NSString *)title SubTitle: (NSString *) subTitle{
coordinate=c;
mTitle = [title retain];
mSubTitle = [subTitle retain];
NSLog(#"%f,%f",c.latitude,c.longitude);
return self;
}
-(void) dealloc{
[super dealloc];
[mTitle release];
[mSubTitle release];
}
#end
#implementation MapViewController
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
/*
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization.
}
return self;
}
*/
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
//------ To Set center of the map ------
CLLocationCoordinate2D center;
center.latitude = 37.83792;
center.longitude = -122.247865;
MKCoordinateRegion region;
MKCoordinateSpan span;
span.latitudeDelta = 0.05;
span.longitudeDelta = 0.05;
region.center = center;
region.span = span;
[mapView setRegion:region animated:YES];
//------ To Add a point of interest ------
CLLocationCoordinate2D c1;
// Point one
c1.latitude = 37.8393624;
c1.longitude = -122.2436549;
AddressAnnotation* ad1 = [[AddressAnnotation alloc] initWithCoordinate:c1 Title:#"title here" SubTitle:#"subtitle here"];
ad1.image = [UIImage imageNamed:#"img01.png"];
[mapView addAnnotation:ad1];
[ad1 release];
// Point two
c1.latitude = 37.835964;
c1.longitude = -122.250538;
AddressAnnotation* ad2 = [[AddressAnnotation alloc] initWithCoordinate:c1 Title:#"title here" SubTitle:#"subtitle here"];
ad2.image = [UIImage imageNamed:#"img02.png"];
[mapView addAnnotation:ad2];
[ad2 release];
// Point three
c1.latitude = 37.8317039;
c1.longitude = -122.2454169;
AddressAnnotation* ad3 = [[AddressAnnotation alloc] initWithCoordinate:c1 Title:#"title here" SubTitle:#"subtitle here"];
ad3.image = [UIImage imageNamed:#"img03.png"];
[mapView addAnnotation:ad3];
[ad3 release];
//----------------------------------------
}
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[AddressAnnotation class]])
{
static NSString *AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if (!pinView)
{
pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier] autorelease];
pinView.canShowCallout = YES;
pinView.animatesDrop = YES;
}
else
{
pinView.annotation = annotation;
}
UIImageView *leftCalloutView = [[UIImageView alloc]
initWithImage:((AddressAnnotation *)annotation).image];
pinView.leftCalloutAccessoryView = leftCalloutView;
[leftCalloutView release];
return pinView;
}
return nil;
}
/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations.
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc. that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
#end
To display your own image instead of the standard pin, create a plain MKAnnotationView instead of a MKPinAnnotationView and set its image property instead of the leftCalloutAccessoryView:
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[AddressAnnotation class]])
{
static NSString *AnnotationIdentifier = #"AnnotationIdentifier";
MKAnnotationView *pinView = [mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if (!pinView)
{
pinView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier] autorelease];
pinView.canShowCallout = YES;
}
else
{
pinView.annotation = annotation;
}
pinView.image = ((AddressAnnotation *)annotation).image;
return pinView;
}
return nil;
}
Note that the MKAnnotationView class doesn't have an animatesDrop property like MKPinAnnotationView so the annotations won't drop on the map. If a drop animation is required, it will have to be done manually (eg. in didAddAnnotationViews).

Issue with Map Annotation and MKMapView in iOS 4.2?

I have a map view with pins that when the user selects a pin it goes to a detail screen for that pin. I also have a table view that when the user selects an item it goes to the same type detail view.
Here's the problem ...
It seems to work fine in everything from 3.1.3 to 4.1. That is the detail view matches with the pin. But I have a user who just upgraded to iOS 4.2 and says that under 4.1 it worked fine, but in 4.2 the pin selection takes him to the detail for a different pin. But in the table view it still works fine.
Is it possible that there was a change in MKMapView in iOS 4.2 that changes how the pinID is selected?
Addition - I have added the relevant parts of viewForAnnotation and checkButtonTapped
- (MKPinAnnotationView *)mapView:(MKMapView *)eMapView viewForAnnotation:(id <MKAnnotation>)annotation {
int postTag = 0;
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[eMapView dequeueReusableAnnotationViewWithIdentifier:#"Pin"];
if(pinView == nil) {
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"Pin"];
pinView.frame = CGRectMake(0, 0, 25, 25);
} else {
pinView.annotation = annotation;
}
// Set up the Right callout
UIButton *myDetailButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
myDetailButton.frame = CGRectMake(0, 0, 23, 23);
myDetailButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
myDetailButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
[myDetailButton addTarget:self action:#selector(checkButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
// Identify which pin is being selected
if ([[annotation title] isEqualToString:#"Current Location"]) {
postTag = 99999;
} else {
postTag = [annotation getPinID];
}
myDetailButton.tag = postTag;
pinView.rightCalloutAccessoryView = myDetailButton;
pinView.animatesDrop = YES;
// Set to show a callout on the pin
pinView.canShowCallout = YES;
return pinView;
}
// Method to show detail view when the callOut button is selected
- (IBAction) checkButtonTapped: (id) sender {
int nrButtonPressed = ((UIButton *)sender).tag;
if (nrButtonPressed < 99999) {
if (self.showDetailView == nil) {
DetailData *tmpViewController = [[DetailData alloc] initWithNibName:#"Detail" bundle:nil];
self.showDetailView = tmpViewController;
[tmpViewController release];
}
buttonDetail = [[mapView annotations] objectAtIndex:(nrButtonPressed-1)];
NSMutableArray *tmpArray = [NSMutableArray arrayWithObjects: [buttonDetail getDataI], [buttonDetail getDataII], [buttonDetail getDataIII], [buttonDetail getDataIV], [buttonDetail getDataV], nil];
self.showDetailView.eventData = [[NSMutableArray alloc] initWithArray:tmpArray copyItems:YES];
[self.navigationController pushViewController:self.showDetailView animated:YES];
[self.showDetailView.eventData release];
}
}
As you can see, in checkButtonTapped I record the pin selected then collect the data from the annotation for that pin. The problem seems to be that nrButtonPressed is now incorrect. But it is fine when compiled in 4.1
In checkButtonTapped, the annotation is being identified using the button's tag. The button's tag is set in viewForAnnotation by calling the custom method getPinID.
The button tag is used as the index into the mapView annotations array. It's not clear how the getPinID method figures out what index it is at in the mapView's annotations array. I'm not sure it's wise to assume an annotation is going to be at a specific index in the array. Even if mapView doesn't shuffle the annotations around, your app might be constantly adding and removing annotations.
Because you are assigning the button as a callout accessory in the annotation view, rather than using a button tag to identify the annotation, you can use the mapView's own mapView:annotationView:calloutAccessoryControlTapped: delegate method.
Instead of doing addTarget on the button and having it call your custom method, remove the call to addTarget and implement calloutAccessoryControlTapped.
In calloutAccessoryControlTapped, you can directly access the annotation using view.annotation. You can also easily check if the annotation is the "user location":
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
if (view.annotation == mapView.userLocation)
return;
buttonDetail = (MyCustomAnnotationClass *)view.annotation;
//show detail view using buttonDetail...
}
Additionally, in viewForAnnotation, you are creating a button every time even if the view is being re-used. So a re-used annotation view probably ends up getting multiple buttons on top of each other. The button should only be created and set in the if(pinView == nil) section.
If anyone would care to see what I am using now (and it is working) ...
- (MKPinAnnotationView *)mapView:(MKMapView *)eMapView viewForAnnotation:(id <MKAnnotation>)annotation {
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[eMapView dequeueReusableAnnotationViewWithIdentifier:#"Pin"];
if(pinView == nil) {
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"Pin"];
pinView.frame = CGRectMake(0, 0, 25, 25);
} else {
pinView.annotation = annotation;
}
// Set up the Right callout
UIButton *myDetailButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
myDetailButton.frame = CGRectMake(0, 0, 23, 23);
myDetailButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
myDetailButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
pinView.rightCalloutAccessoryView = myDetailButton;
pinView.animatesDrop = YES;
// Set to show a callout on the pin
pinView.canShowCallout = YES;
return pinView;
}
// Added this at the suggestion of aBitObvious on StackOverflow - 120810
- (void)mapView:(MKMapView *)eMapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
if (view.annotation == eMapView.userLocation)
return;
if (self.showDetailView == nil) {
DetailData *tmpViewController = [[DetailData alloc] initWithNibName:#"DetailData" bundle:nil];
self.showDetailView = tmpViewController;
[tmpViewController release];
}
buttonDetail = (MyCustomAnnotationClass *)view.annotation;
NSMutableArray *tmpArray = [NSMutableArray arrayWithObjects: [buttonDetail getDataI], [buttonDetail getDataII], [buttonDetail getDataIII], [buttonDetail getDataIV], [buttonDetail getDataV], nil];
self.showDetailView.eventData = [[NSMutableArray alloc] initWithArray:tmpArray copyItems:YES];
[self.navigationController pushViewController:self.showDetailView animated:YES];
[self.showDetailView.eventData release];
}
Thanks again!