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).
Related
i'm working on a custom annotationview that show a image of background, with inside a custom image.
I've based my code from this: Custom MKAnnotationView with frame,icon and image
And my actually code is:
else if ([annotation isKindOfClass:[ImagePin class]]){
static NSString *identifierImage = #"ImagePinLocation";
MKAnnotationView *annotationImageView = (MKAnnotationView *) [self.mapView dequeueReusableAnnotationViewWithIdentifier:identifierImage];
if(annotationImageView){
return annotationImageView;
}
else {
annotationImageView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifierImage];
annotationImageView.canShowCallout = YES;
annotationImageView.image = [UIImage imageNamed:#"image_bg_mappa"];
UIImage *frame = [UIImage imageNamed:#"image_bg_mappa"];
ImagePin *imagePinImage = annotation;
NSLog(#"%#", [NSString stringWithFormat:#"%#", imagePinImage.image]);
NSString *urlStringForImage = [NSString stringWithFormat:#"%#", imagePinImage.image];
NSURL *urlForImage = [NSURL URLWithString:urlStringForImage];
UIImageView *imageView = [[UIImageView alloc] init];
[imageView setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#", urlForImage]] placeholderImage:[UIImage imageNamed:#"avatar-placeholder.png"]];
UIGraphicsBeginImageContext(CGSizeMake(annotationImageView.image.size.width, annotationImageView.image.size.height));
[frame drawInRect:CGRectMake(0, 0, frame.size.width, frame.size.height)];
[imageView.image drawInRect:CGRectMake(2, 2, 48, 38)]; // the frame your inner image
annotationImageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self action:#selector(writeSomething:) forControlEvents:UIControlEventTouchUpInside];
[rightButton setTitle:#"" forState:UIControlStateNormal];
annotationImageView.canShowCallout = YES;
annotationImageView.rightCalloutAccessoryView = rightButton;
annotationImageView.enabled = YES;
return annotationImageView;
}
}
And then i have two method:
(void)writeSomething {
}
(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
}
But:
Not show my rightButton callout(not show also the button)
I prefer that when i click on the full image/annotation it call mapView annotationView method.
How i can do it ? Why when i click on annotation, it not call my calloutAccessoryControlTapped method ?
In this map i have also more different type of pin(mkpinannotation), and works fine(also the method calloutAccessoryControlTapped works fine with these pins).
Where is the problem ?
Thanks to all, and sorry for my bad english(I'm learning it).
EDIT: My full code for viewForAnnotation method: https://gist.github.com/anonymous/6224519e308d6bf56c4c
The MKPinAnnotation works fine.
EDIT:
This is my ImagePin.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface ImagePin : NSObject <MKAnnotation>
- (id)initWithImage:(NSString*)image imagebeachId:(NSString*)imagebeachId coordinate:(CLLocationCoordinate2D)coordinate;
//- (MKMapItem*)mapItem;
#property (nonatomic, copy) NSString *imagebeachId;
#property (nonatomic, copy) NSString *image;
#property (nonatomic, assign) CLLocationCoordinate2D theCoordinate;
#end
And my ImagePin.m
#import "ImagePin.h"
#import <AddressBook/AddressBook.h>
#interface ImagePin ()
#end
#implementation ImagePin
#synthesize image = _image;
#synthesize imagebeachId = _imagebeachId;
#synthesize theCoordinate = _theCoordinate;
- (id)initWithImage:(NSString*)image imagebeachId:(NSString*)imagebeachId coordinate:(CLLocationCoordinate2D)coordinate {
if ((self = [super init])) {
//self.address = address;
self.image = image;
self.imagebeachId = imagebeachId;
self.theCoordinate = coordinate;
}
return self;
}
- (NSString *)title {
return #"";
}
- (NSString *)subtitle {
return _image;
}
- (CLLocationCoordinate2D)coordinate {
return _theCoordinate;
}
#end
EDIT:
This is where i add annotation:
- (void)imagePositions:(NSDictionary *)responseData {
for (id<MKAnnotation> annotation in self.mapView.annotations) {
if ([annotation isKindOfClass:[ImagePin class]]){
//[self.mapView removeAnnotation:annotation];
}
}
for (NSArray *row in responseData) {
NSString * imageBeachId = [row valueForKey:#"id"];
NSNumber * latitude = [row valueForKey:#"lat"];
NSNumber * longitude = [row valueForKey:#"lng"];
NSString * imageBeach = [row valueForKey:#"fb_photo_url"];
CLLocationCoordinate2D coordinate;
coordinate.latitude = latitude.doubleValue;
coordinate.longitude = longitude.doubleValue;
ImagePin *annotation = [[ImagePin alloc] initWithImage:imageBeach imagebeachId:imageBeachId coordinate:coordinate];
[self.mapView addAnnotation:annotation];
}
}
I have fixed all!
The problem is that title can't be empty(i was using #""... with a #"AAA works fine) and then for not show the callout i have done:
annotationView.canShowCallout = NO;
and implemented:
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
//here you action
}
A BIG thanks to Anna(the solution is your)!
First checkout that you give the delegate to MapView or not , if not then give the delegate like bellow..
yourMapView.delegate = self;
Also in .h file define that delegate like bellow..
#interface ViewController : UIViewController<MKMapViewDelegate,MKAnnotation>{...
after check that this bellow method called when accessory tapped...
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
NSLog(#"Do something ");
}
UPDATE:
Instead of Custom Button try to use it...
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
NSLog(#"viewForAnnotation...");
static NSString *identifier = #"MyLocation";
if ([annotation isKindOfClass:[ImagePin class]]) {
MKAnnotationView *annotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil)
{
annotationView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier] autorelease];
}
if (annotation == mapView.userLocation)
return nil;
annotationView.image = [UIImage imageNamed:#"yourImageName.png"];
annotationView.annotation = annotation;
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView.tag = 101;
annotationView.canShowCallout = YES;
// annotationView.animatesDrop = YES;
return annotationView;
}
return nil;
}
For anyone else that stumbles upon this post while implementing a custom MKAnnotationView, the problem may also be that you have to explicitly set the frame of your view.
MKMapView MKPointAnnotation tap event not called
I have made these pieces of code for my custom annotation:
- (void)viewDidLoad
{
[super viewDidLoad];
mapView.delegate=self;
[self AddLocations];
[self initiateMap];
......
this is my addLocation function:
-(id<MKAnnotation>)addAnnotationWithTitle: (NSString*) title coordinate:(CLLocationCoordinate2D)
Location imageName:(NSString*) imageName{
CustomAnnotation *annotation = [[CustomAnnotation alloc]init];
annotation.title = title;
annotation.subtitle = #"This is a subtitle";
[annotation setCoordinate:Location];
annotation.imageName = imageName;
return annotation;
}
and my CustomAnnotation Class:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
NSLog(#"Test");
if([annotation isKindOfClass:[MKUserLocation class]]){
return nil;
}
if([annotation isKindOfClass:[CustomAnnotation class]]){
CustomAnnotation *customAnnotation = annotation;
static NSString* annotationid = #"Annotation";
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:annotationid];
if(!annotationView){
annotationView = [[MKAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:annotationid];
annotationView.canShowCallout = YES;
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}else{
annotationView.annotation = annotation;
}
annotationView.image = [UIImage imageNamed:customAnnotation.imageName];
return annotationView;
}
return nil;
}
For some reason the viewForAnnotation function is never used. Can anyone help me out here?
Thanks
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.
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.
I am new with MapKit and all its functionalities and therefore, stuck at trying to display a pin. I followed an online video tutorial on how to find user current location and drop a pin there. But when I typed the method for the pin, I do not get it at all. I would like to know where did I go wrong for this.
- (void)viewDidLoad{
[super viewDidLoad];
self.title = #"Location";
mapView.showsUserLocation = YES;
self.locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
[locationManager startUpdatingHeading];
}
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
NSLog(#"Annotation view run");
MKPinAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"currentloc"];
pin.animatesDrop = YES;
return pin;
}
-(void) locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
// once location is determined, center to that location.
location = newLocation.coordinate;
MKCoordinateRegion region;
region.center = location;
MKCoordinateSpan span;
span.latitudeDelta = 0.003;
span.longitudeDelta = 0.003;
region.span = span;
[mapView setRegion:region animated:FALSE];
}
Any advice of where I done wrong would be great!
As your method is never got invoked, you should do:
- (void)mapView:(MKMapView *)aMapView regionDidChangeAnimated:(BOOL)animated {
[aMapView removeAnnotation:[self point]];
mkShape.coordinate = aMapView.centerCoordinate;
[aMapView addAnnotation:mkShape];
}
And you should create your MKAnnotationView properly.
You need create two properties in .h file first (for performance reason):
MKPointAnnotation *mkShape;
MKAnnotationView *annotationView;
then use the code below to create your MKPointAnnotation
- (void)createShape
{
if (!mkShape) {
mkShape = [[MKPointAnnotation alloc] init];
mkShape.title = nil;
mkShape.subtitle = #"test description";
}
}
- (id <MKAnnotation>)point
{
[self createShape];
// Make sure to check if this is an MKPointAnnotation. MKOverlays also
// conform to MKAnnotation, so it isn't sufficient to just check to
// conformance to MKAnnotation.
if ([mkShape isKindOfClass:[MKPointAnnotation class]])
return (id <MKAnnotation>)mkShape;
return nil;
}
- (MKAnnotationView *)annotationView
{
if (!annotationView) {
id <MKAnnotation> annotation = [self point];
if (annotation) {
MKPinAnnotationView *pin =
[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:nil];
pin.canShowCallout = YES;
pin.animatesDrop = YES;
pin.draggable = NO;
annotationView = pin;
}
}
return annotationView;
}
- (MKAnnotationView *)mapView:(MKMapView *)aMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isEqual:mkShape]) {
return [self annotationView];
}
return nil;
}
At the end, don't forget to release your properties.
- (void)dealloc
{
[annotationView release];
[mkShape release];
[super dealloc];
}