Adding a custom annotation to a MKMapview - ios

I am trying to add annotations to a MKMapView
I have created a class CustomMapPin which conforms to the MKAnnotation protocol
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface CustomMapPin : NSObject <MKAnnotation> {
CLLocationCoordinate2D coordinate;
NSString *title;
NSString *subtitle;
}
#property(nonatomic, assign) CLLocationCoordinate2D coordinate;
#property(nonatomic, copy) NSString *title;
#property(nonatomic, copy) NSString *subtitle;
#property(nonatomic, strong) NSString *type; // this is to differentiate between the different annotations on the map
#end
I have created a class CustomMapAnnotationView which is a subclass of MKAnnotationView
CustomMapAnnotationView.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface CustomMapAnnotationView : MKAnnotationView
#property (nonatomic, strong) UIImageView *annotationImage;
#end
CustomMapAnnotationView.m
#import "CustomMapAnnotationView.h"
#implementation CustomMapAnnotationView
-(id) initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self) {
self.frame = CGRectMake(0, 0, 28, 40);
self.backgroundColor = [UIColor clearColor];
self.annotationImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 28, 40)];
self.annotationImage.contentMode = UIViewContentModeScaleAspectFit;
[self addSubview:self.annotationImage];
}
return self;
}
#end
I am adding the custom pins inside FindMechanicViewController which is a CLLocationManagerDelegate and MKMapViewDelegate
The code snippet is:
-(void) viewWillAppear:(BOOL)animated {
[self.locationManager startUpdatingLocation];
self.currentLocation = [self.locationManager location];
// Set the region of the map
MKCoordinateSpan mapViewSpan = MKCoordinateSpanMake(0.01, 0.01);
MKCoordinateRegion mapRegion = MKCoordinateRegionMake(self.currentLocation.coordinate, mapViewSpan);
[self.mapView setRegion:mapRegion];
// Add custom pin showing user location
CustomMapPin *annotation = [[CustomMapPin alloc] init];
annotation.title = #"Current Location";
annotation.coordinate = self.currentLocation.coordinate;
annotation.type = #"user location";
[self.mapView addAnnotation:annotation];
}
And the delegate method
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
static NSString *reuseId = #"CustomMapPin";
CustomMapAnnotationView *annotationView = (CustomMapAnnotationView *) [self.mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if ([annotation isKindOfClass:[CustomMapPin class]]) {
CustomMapPin *customAnnotation = (CustomMapPin *)annotation;
if ([customAnnotation.type isEqualToString:#"user location"]) {
[annotationView setAnnotation:customAnnotation];
[annotationView setImage:[UIImage imageNamed:#"pin_user"]];
[annotationView setCanShowCallout:YES];
}
}
return annotationView;
}
This does not show anything on the map. How do I fix this ?

In viewForAnnotation, CustomMapAnnotationView is never actually alloc+inited (the dequeueReusableAnnotationViewWithIdentifier does not do this).
If viewForAnnotation is even getting called, it must be returning nil which means the map view must be putting a red pin somewhere (and if the delegate method isn't getting called, then again the map view will default to a red pin). Log the coordinates where the annotation is being added and look there.
A corrected viewForAnnotation might look like this:
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if ([annotation isKindOfClass:[CustomMapPin class]]) {
static NSString *reuseId = #"CustomMapPin";
CustomMapAnnotationView *annotationView = (CustomMapAnnotationView *) [self.mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (!annotationView) {
annotationView = [[CustomMapAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
[annotationView setCanShowCallout:YES];
}
CustomMapPin *customAnnotation = (CustomMapPin *)annotation;
//view's annotation should be set regardless of "type"
[annotationView setAnnotation:customAnnotation];
if ([customAnnotation.type isEqualToString:#"user location"]) {
[annotationView setImage:[UIImage imageNamed:#"pin_user"]];
}
else {
//If it's not a "user location", then set image
//to something else otherwise image will either be nil
//or it will show some other annotation's image...
[annotationView setImage:[UIImage imageNamed:#"pin_other"]];
}
return annotationView;
}
return nil;
}
Some other issues with the code:
In viewWillAppear, code is retrieving the location immediately after calling startUpdatingLocation. This is not guaranteed to work every time and even if it does "work", you will most likely be getting an old, cached location. When it doesn't work, the annotation will either end up at 0,0 (Atlantic Ocean) or app will crash due to invalid coordinates. It's much better to read the location in the didUpdateLocations delegate method. In viewWillAppear, call startUpdatingLocation and then in didUpdateLocations, if the accuracy and age of the location is adequate for you, call stopUpdatingLocation and then use the location (create and add the annotation, etc).
In CustomMapAnnotationView, annotationImage object is created and added but its image property is never set. The annotationImage is actually never really used for anything.
The whole CustomMapAnnotationView class is unnecessary for your purpose . In viewForAnnotation, code is setting the image property on the CustomMapAnnotationView (instead of using annotationImage). This image property is inherited from MKAnnotationView. The built-in, basic MKAnnotationView class is all you need for what you're doing. It already has an image property and it automatically displays it for you (you don't need to create your own UIImageView).

Related

MKAnnotationView image changing issue

I've got an MKMapView loaded with plenty of custom annotation (800 circa).
When I drag on the map and I return to an annotation, it's image has changed with another one. For me it's seems like a cache issue.
Pin before dragging
Pin after dragging
SuperClass header
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface MapAnnotation : NSObject <MKAnnotation>
#property (nonatomic, copy) NSString *title;
#property NSString * pinImageName;
#property (nonatomic) CLLocationCoordinate2D coordinate;
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate title:(NSString *)title pinImageName:(NSString *)pinImageName;
- (MKAnnotationView *)getAnnotationView;
#end
SubClass (the one that creates the issue) header
#import "MapAnnotation.h"
#import "Company.h"
#interface CompanyAnnotation : MapAnnotation
#property Company *company;
#property UIImage *pinImage;
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate title:(NSString *)title pinImage:(UIImage *)pinImage;
- (MKAnnotationView *)getAnnotationView;
#end
viewForAnnotation delegate method
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation {
if (annotation == mapView.userLocation){
return nil;
}
MapAnnotation *location = [MapAnnotation new];
MKAnnotationView *annotationView = [MKAnnotationView new];
if ([annotation isKindOfClass:[CompanyAnnotation class]]) {
location = (CompanyAnnotation *)annotation;
annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"companyAnnotation"];
}
if (annotationView == nil) {
annotationView = [location getAnnotationView];
} else {
annotationView.annotation = annotation;
}
return annotationView;
}
getAnnotationView method
- (MKAnnotationView *)getAnnotationView {
MKAnnotationView * annotationView = [[MKAnnotationView alloc] initWithAnnotation:self reuseIdentifier:#"companyAnnotation"];
[annotationView setEnabled:YES];
[annotationView setCanShowCallout:YES];
[annotationView setContentMode:UIViewContentModeScaleAspectFit];
[annotationView setImage:self.pinImage];
[annotationView setFrame:CGRectMake(0, 0, 28, 44)];
[annotationView setRightCalloutAccessoryView:[UIButton buttonWithType:UIButtonTypeDetailDisclosure]];
return annotationView;
}
The dequeue is not being handled properly in viewForAnnotation because when dequeueReusableAnnotationViewWithIdentifier returns a previously-used view (when annotationView is not nil), the code is only updating that view's annotation property:
if (annotationView == nil) {
annotationView = [location getAnnotationView];
} else {
annotationView.annotation = annotation;
}
But the annotation view's image is not updated -- in a dequeued view, the image will be set to the one associated with the annotation the view was originally created for (when getAnnotationView was called).
So now the view appears at the new annotation's coordinates but the image is still from the previous annotation the view was used for.
There are various ways to fix this such as creating a proper subclass of MKAnnotationView that monitors changes to its annotation property and automatically updates all other properties associated with an annotation.
With the existing code, a simple way to fix it is to separate out the annotation-specific property changes into a separate method that can be called both when the view is created and when its annotation property is updated.
For example, in CompanyAnnotation, create a method like this:
-(void)configureView:(MKAnnotationView *)av
{
av.image = self.pinImage;
}
Then change getAnnotationView to:
- (MKAnnotationView *)getAnnotationView {
MKAnnotationView * annotationView = [[MKAnnotationView alloc] initWithAnnotation:self reuseIdentifier:#"companyAnnotation"];
//set properties here that are not specific to an annotation...
[annotationView setEnabled:YES];
[annotationView setCanShowCallout:YES];
[annotationView setContentMode:UIViewContentModeScaleAspectFit];
//[annotationView setImage:self.pinImage];
[annotationView setFrame:CGRectMake(0, 0, 28, 44)];
[annotationView setRightCalloutAccessoryView:[UIButton buttonWithType:UIButtonTypeDetailDisclosure]];
//set properties that are specific to an annotation...
[self configureView:annotationView];
return annotationView;
}
Finally in viewForAnnotation:
if (annotationView == nil) {
annotationView = [location getAnnotationView];
} else {
annotationView.annotation = annotation;
if ([annotation isKindOfClass:[CompanyAnnotation class]]) {
//update image, etc...
[annotation configureView:annotationView];
}
}
Note that MapAnnotation will have the same issue with the pinImageName property.

dynamically change leftCalloutAccessoryView based on the MKAnnotationView that is selected

I have an array of images, that are associated with each Annotation on my map. I can statically add an image to the leftCalloutAccessoryView but I am unsure how to make this dynamic. I hope its clear what I am asking. Each annotation has its own individual image that I want to display but I am unsure of how to reference the image in the following method;
- (MKAnnotationView *)mapView:(MKMapView *)mv viewForAnnotation:(id <MKAnnotation>)annotation
{
if([annotation isKindOfClass:[MKUserLocation class]])
return nil;
NSString *annotationIdentifier = #"PinViewAnnotation";
MyAnnotationView *pinView = (MyAnnotationView *) [mv dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
if (!pinView)
{
pinView = [[MyAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotationIdentifier];
pinView.canShowCallout = YES;
UIImageView *houseIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Icon"]];//static image
[houseIconView setFrame:CGRectMake(0, 0, 30, 30)];
pinView.leftCalloutAccessoryView = houseIconView;
}
else
{
pinView.annotation = annotation;
}
return pinView;
}
My array "self.sandwiches" contains Sandwich objects that have a name (NSString) and an imageName ('NSString').
Im looking for a solution where I can get the index of the pin that is selected, similar to a UITableView where you can get its index, and access it from the array using indexPath.row.
My Annotation class;
.h
#import
#import
#import
#interface SandwichAnnotation : NSObject<MKAnnotation>
#property (nonatomic,assign) CLLocationCoordinate2D coordinate;
#property (nonatomic,copy) NSString * title;
#property (nonatomic,copy) NSString * subtitle;
#end
.m
#import "SandwichAnnotation.h"
#implementation SandwichAnnotation
#synthesize coordinate,title,subtitle;
#end
In viewForAnnotation, rather than "getting the index of the pin" (which would work but is less efficient here than with a UITableView), I suggest adding the data required to the annotation class itself.
This way, the data is more self-contained and the code in the delegate method or elsewhere doesn't need to worry, know, or be kept in sync with where or what kind of structure the annotation object is stored in. As long as you have a reference to the annotation object, you will immediately have all the data needed for that annotation (or at least it will contain references to the related data within itself).
The viewForAnnotation delegate method provides a reference to the annotation object it needs a view for (the annotation parameter). It's typed generically as id<MKAnnotation> but it is actually an instance of the exact type that was created (either SandwichAnnotation by you or MKUserLocation by the map view).
One option is to make the parent Sandwich class itself implement MKAnnotation and eliminate the SandwichAnnotation class. This way, no searching or references are needed at all since the annotation parameter will actually be a Sandwich.
However, you may want to keep a separate class for your annotation objects (which is fine). In this case, you can add a reference to the parent object(s) in the annotation class. Example:
#interface SandwichAnnotation : NSObject<MKAnnotation>
#property (nonatomic,assign) CLLocationCoordinate2D coordinate;
#property (nonatomic,copy) NSString * title;
#property (nonatomic,copy) NSString * subtitle;
#property (nonatomic,retain) Sandwich * whichSandwich; // <-- add reference
#end
When creating a SandwichAnnotation, set the reference to which Sandwich the annotation is for:
for (Sandwich *currentSandwich in self.sandwiches) {
SandwichAnnotation *sa = [[SandwichAnnotation alloc] init...];
sa.coordinate = ...
sa.title = ...
sa.whichSandwich = currentSandwich; // <-- set reference
[mapView addAnnotation:sa];
}
Finally, in viewForAnnotation, if annotation is of type SandwichAnnotation, set the leftCalloutAccessoryView:
- (MKAnnotationView *)mapView:(MKMapView *)mv viewForAnnotation:(id <MKAnnotation>)annotation
{
if (! [annotation isKindOfClass:[SandwichAnnotation class]]) {
//If annotation is not a SandwichAnnotation, return default view...
//This includes MKUserLocation.
return nil;
}
//At this point, we know annotation is of type SandwichAnnotation.
//Cast it to that type so we can get at the custom properties.
SandwichAnnotation *sa = (SandwichAnnotation *)annotation;
NSString *annotationIdentifier = #"PinViewAnnotation";
MyAnnotationView *pinView = (MyAnnotationView *) [mv dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
if (!pinView)
{
pinView = [[MyAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotationIdentifier];
pinView.canShowCallout = YES;
//Here, just initialize a blank UIImageView ready to use.
//Set image below AFTER we have a dequeued or new view ready.
UIImageView *houseIconView = [[UIImageView alloc] init];
[houseIconView setFrame:CGRectMake(0, 0, 30, 30)];
pinView.leftCalloutAccessoryView = houseIconView;
}
else
{
pinView.annotation = annotation;
}
//At this point, we have a dequeued or new view ready to use
//and pointing to the correct annotation.
//Update image on the leftCalloutAccessoryView here
//(not just when creating the view otherwise an annotation
//that gets a dequeued view will show an image of another annotation).
UIImageView *houseIconView = (UIImageView *)pinView.leftCalloutAccessoryView;
NSString *saImageName = sa.whichSandwich.imageName;
UIImage *houseIcon = [UIImage imageNamed: saImageName];
if (houseIcon == nil) {
//In case the image was not found,
//set houseIcon to some default image.
houseIcon = someDefaultImage;
}
houseIconView.image = houseIcon;
return pinView;
}

Callout opening wrong view after zoom in iOS7

Everything is working fine in my app except for one thing: after zooming in and zooming back out, to see the whole map, some callouts open the wrong detailview.
I don't know if I'm missing some code or else.
Using Xcode 5.1.1 for iOS7.
This is what I've got at the moment:
Annotation.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface Annotation: NSObject <MKAnnotation>
#property (nonatomic, assign) CLLocationCoordinate2D coordinate;
#property (nonatomic, copy) NSString *title;
#property (nonatomic, copy) NSString *subtitle;
#end
Annotation.m
#import "Annotation.h"
#implementation Annotation
#synthesize coordinate,title,subtitle;
#end
MapView.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface Nameofthemap : UIViewController <MKMapViewDelegate>
#property (strong, nonatomic) IBOutlet MKMapView *Nameofthemap;
#end
MapView.m
#import "MapView.h"
#import "Annotation.h"
#import "InfoViewController.h"
#import "InfoTwoViewController.h"
#interface MapView ()
#property (nonatomic, strong) IBOutlet InfoViewController *InfoViewController;
#property (nonatomic, strong) IBOutlet InfoTwoViewController *InfoTwoViewController;
#end
#define PLACE1_LATITUDE 43.777130;
#define PLACE2_LONGITUDE 10.790018;
#define PLACE2_LATITUDE 43.81471237;
#define PLACE2_LONGITUDE 10.67472765;
#implementation MapView
- (IBAction)changeMapType:(id)sender {
if (_MapView.mapType == MKMapTypeHybrid)
_MapView.mapType = MKMapTypeStandard;
else
_MapView.mapType = MKMapTypeHybrid;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self gotoLocation];
_MapView.showsUserLocation = YES;
}
- (void)gotoLocation
{
MKCoordinateRegion newRegion;
newRegion.center.latitude = PLACE1_LATITUDE;
newRegion.center.longitude = PLACE2_LONGITUDE;
newRegion.span.latitudeDelta = 0.25f;
newRegion.span.longitudeDelta = 0.25f;
[self.MapView setRegion:newRegion animated:YES];
NSMutableArray * locations = [[NSMutableArray alloc] init];
CLLocationCoordinate2D location;
Annotation *myAnn;
Annotation *myAnn2;
//Place1 annotation
myAnn = [[Annotation alloc] init];
location.latitude = PLACE1_LATITUDE;
location.longitude = PLACE1_LONGITUDE;
myAnn.coordinate = location;
myAnn.title = #"Name of the place";
myAnn.subtitle = #"Details";
[locations addObject:myAnn];
//Place2 annotation
myAnn2 = [[Annotation alloc] init];
location.latitude = PLACE2_LATITUDE;
location.longitude = PLACE2_LONGITUDE;
myAnn2.coordinate = location;
myAnn2.title = #"Name of place two";
myAnn2.subtitle = #"Details";
[locations addObject:myAnn2];
[self->_MapView addAnnotations:locations];
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)myAnn {
if ([myAnn isKindOfClass:[MKUserLocation class]])
{
((MKUserLocation *)myAnn).title = #"Your position";
return nil;
}
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"pinView"];
if (!pinView) {
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:myAnn reuseIdentifier:#"pinView"];
pinView.pinColor = MKPinAnnotationColorRed;
pinView.canShowCallout = YES;
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
if ([[myAnn title] isEqualToString:#"Name of the place"]){
[rightButton addTarget:self action:#selector(myAnnClicked:)forControlEvents:UIControlEventTouchUpInside];
}
if ([[myAnn title] isEqualToString:#"Name of place two"]){
[rightButton addTarget:self action:#selector(myAnn2Clicked:)forControlEvents:UIControlEventTouchUpInside];
}
pinView.rightCalloutAccessoryView = rightButton;
}
return pinView;
}
-(IBAction)myAnnClicked:(id)sender
{
InfoViewController *info = [[InfoViewController alloc]init];
[self.navigationController pushViewController:info animated:YES];
}
-(IBAction)myAnn2Clicked:(id)sender
{
InfoTwoController *info2 = [[InfoTwoController alloc]init];
[self.navigationController pushViewController:info2 animated:YES];
}
#end
It's an annotation view re-use issue.
In viewForAnnotation, the button targets are only being set when creating a view (if dequeueReusableAnnotationViewWithIdentifier returns nil).
But if dequeueReusableAnnotationViewWithIdentifier returns a previously-used view, the button target is still whatever was set for the annotation that used the view before.
That previous annotation may not be the same as the current annotation.
So it's possible for annotation "two" to re-use a view that was originally created for annotation "one" and tapping on the already-created button shows the info for "one" instead of "two".
To fix this, two things should be done:
If dequeueReusableAnnotationViewWithIdentifier returns a view (if pinView is not nil), the code must update the view's annotation property to the current annotation.
The button target must be set whether a new view is being created or a dequeued view is being re-used. The easiest way to do this is to move the button creation/setting after the main if and just before the return.
The updated viewForAnnotation would look like this:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)myAnn {
if ([myAnn isKindOfClass:[MKUserLocation class]])
{
((MKUserLocation *)myAnn).title = #"Your position";
return nil;
}
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"pinView"];
if (!pinView) {
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:myAnn reuseIdentifier:#"pinView"];
pinView.pinColor = MKPinAnnotationColorRed;
pinView.canShowCallout = YES;
}
else
{
//1. Re-using a view, update which annotation it's being used for now
pinView.annotation = myAnn;
}
//2. Now pinView is either a new view or re-used view.
//Set its button target based on current annotation...
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
if ([[myAnn title] isEqualToString:#"Name of the place"]){
[rightButton addTarget:self action:#selector(myAnnClicked:)forControlEvents:UIControlEventTouchUpInside];
}
if ([[myAnn title] isEqualToString:#"Name of place two"]){
[rightButton addTarget:self action:#selector(myAnn2Clicked:)forControlEvents:UIControlEventTouchUpInside];
}
pinView.rightCalloutAccessoryView = rightButton;
return pinView;
}
By the way, instead of creating separate methods for each annotation (which can get tedious), use the map view's calloutAccessoryControlTapped delegate method instead.
In fact, right now, the map view is calling both your custom methods and the calloutAccessoryControlTapped delegate method (in which there's no code currently).
In the delegate method, the annotation tapped is accessible via view.annotation.
So in viewForAnnotation, you would just do this:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)myAnn {
if ([myAnn isKindOfClass:[MKUserLocation class]])
{
((MKUserLocation *)myAnn).title = #"Your position";
return nil;
}
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"pinView"];
if (!pinView) {
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:myAnn reuseIdentifier:#"pinView"];
pinView.pinColor = MKPinAnnotationColorRed;
pinView.canShowCallout = YES;
pinView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
else
{
pinView.annotation = myAnn;
}
return pinView;
}
Then in the calloutAccessoryControlTapped delegate method, you can do something like this:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
if ([view.annotation isKindOfClass:[Annotation class]])
{
Annotation *myAnn = (Annotation *)view.annotation;
id vcToPush = nil;
if ([[myAnn title] isEqualToString:#"Name of the place"]) {
vcToPush = [[InfoViewController alloc]init];
}
else if ([[myAnn title] isEqualToString:#"Name of place two"]) {
vcToPush = [[InfoTwoController alloc]init];
}
[self.navigationController pushViewController:vcToPush animated:YES];
}
}
Then remove the myAnnClicked and myAnn2Clicked methods.
You would also be much better off creating a generic "Info" view controller instead of a separate one for each annotation.
Some other unrelated things:
Don't put a semi-colon at the end of the #define lines
You've defined PLACE2_LONGITUDE twice
newRegion.center is using PLACE2_LONGITUDE instead of PLACE1_LONGITUDE

didSelectAnnotationView method creates an error

In my app, I have a mapView and few other components.
I have changed the view of annotation into my own icons, the icons gets displayed in the specific location of the the latitude and longitude.
But, when I click and hold the icons, it automatically gets converted into the anotations.
Please help me.
This is my map while loading
It becomes as such When I click and hold the annotations
didUpdateUserLocation
{
CLLocationCoordinate2D first;
first.latitude=13.040202;
first.longitude=80.24298;
myAnnotation.coordinate=first;
[locations addObject:myAnnotation];
[self.mapView addAnnotations:locations];
}
viewForAnnotation:
{
static NSString *identifier = #"Wifintech";
pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if ( pinView == nil )
pinView = [[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:identifier];
pinView.image = [UIImage imageNamed:#"car-side.png"];
pinView.canShowCallout = YES;
}
didSelectAnnotationView
{
float latitude = [[view annotation ] coordinate].latitude;
float longitude = [[view annotation ] coordinate].longitude;
title_value=[[view annotation] title];
NSString * subtitle_val =[[view annotation] subtitle];
title_para.text=[NSString stringWithFormat:#"%#, %#",title_value,subtitle_val];
latitude_value.text=[NSString stringWithFormat:#"%f",latitude];
longitude_value.text=[NSString stringWithFormat:#"%f",longitude];
}
When using your own images, you need to create an MKAnnotationView not an MKPinAnnotationView. Please try searching on SO for answers before posting questions (it will save everyone including you a lot of time) .
MKAnnotationView ClassReference
Try returning a MKAnnotationView instead of MKPinAnnotationView from
-mapView:viewForAnnotation:
Most likely the subclass overrides some method that are tracking the selected-state of the view and adjusting the image. I suspect this will not be the case when using MKAnnotationView.
I think you need to take a close look at the following documentation.
Also, maybe make sure that the view in didSelect.. method is actually a pinAnnotionView and that the image is what you think it is.
From the documentation on MKAnnotationView:
Annotation views support the concept of a selection state, which determines whether the view is unselected, selected, or selected and displaying a standard callout view. The user toggles between the selection states through interactions with the annotation view. In the unselected state, the annotation view is displayed but not highlighted. In the selected state, the annotation is highlighted but the callout is not displayed. And finally, the annotation can be displayed both with a highlight and a callout. The callout view displays additional information such as a title string and controls for viewing more information. The title information is provided by the annotation object but your annotation view is responsible for providing any custom controls. For more information, see the subclassing notes.
This is my code
#import "FirstViewController.h"
#import "LocationObject.h"
#define THE_SPAN 0.01f;
#interface FirstViewController ()
#property (weak, nonatomic) IBOutlet MKMapView *mapView;
#property (weak, nonatomic) IBOutlet UITextField *latitude_value;
#property (weak, nonatomic) IBOutlet UITextField *longitude_value;
#property (copy, nonatomic) NSString *title_value;
#property(nonatomic,assign) CLLocationCoordinate2D myCoordinate;
#property(nonatomic,copy) NSMutableArray *locations;
#property(nonatomic,retain) MKPointAnnotation * myAnnotation;
#property (readwrite) int tag;
#property(retain) MKPinAnnotationView *pinView;
#property (weak, nonatomic) IBOutlet UITextView *title_para;
#property(assign) CLLocationCoordinate2D *coordsArray;
#property(nonatomic,assign) MKPolyline * routeLine;
#end
#implementation FirstViewController
#synthesize latitude_value;
#synthesize longitude_value;
#synthesize title_value;
#synthesize myCoordinate;
#synthesize mapView;
#synthesize locations;
#synthesize myAnnotation;
#synthesize tag;
#synthesize pinView;
#synthesize title_para;
#synthesize title;
#synthesize coordsArray;
#synthesize routeLine;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[self setMapView:nil];
[self setLatitude_value:nil];
[self setLongitude_value:nil];
[self setLatitude_value:nil];
[self setLongitude_value:nil];
[self setTitle_value:nil];
[self setTitle_para:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay {
MKPolylineView *polyLineView = [[MKPolylineView alloc] initWithPolyline:routeLine];
polyLineView.fillColor = [UIColor blueColor];
polyLineView.strokeColor = [UIColor redColor];
polyLineView.lineWidth = 2;
return polyLineView;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
//map view : region nad annotation
#pragma mark map view delegate methods
-(void) mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
//map region and center location
MKCoordinateRegion myRegion;
CLLocationCoordinate2D center;
center.latitude=13.040223;
center.longitude=80.240995;
MKCoordinateSpan span;
span.latitudeDelta=THE_SPAN;
span.longitudeDelta=THE_SPAN;
myRegion=MKCoordinateRegionMake(center, span);
MKCoordinateRegion adjusted_region=[self.mapView regionThatFits:myRegion];
[self.mapView setRegion:adjusted_region animated:YES];
locations=[[NSMutableArray alloc]init];
//first point
CLLocationCoordinate2D first;
first.latitude=13.040202;
first.longitude=80.24298;
myAnnotation = [[MKPointAnnotation alloc]init];
myAnnotation.coordinate=first;
myAnnotation.title=#"Wifin Technology";
myAnnotation.subtitle=#"Globus";
[locations addObject:myAnnotation];
//second point
CLLocationCoordinate2D second;
second.latitude=13.0406527;
second.longitude=80.2437427;
myAnnotation= [[MKPointAnnotation alloc]init];
myAnnotation.coordinate=second;
myAnnotation.title=#"The Residency Towers";
myAnnotation.subtitle=#"Chennai";
[locations addObject:myAnnotation];
//third point
CLLocationCoordinate2D third;
third.latitude=13.040202;
third.longitude=80.240191;
myAnnotation= [[MKPointAnnotation alloc]init];
myAnnotation.coordinate=third;
myAnnotation.title=#"Rado Cool Zone";
myAnnotation.subtitle=#"Chennai";
[locations addObject:myAnnotation];
[self.mapView addAnnotations:locations];
title_para.text=[[NSString alloc]initWithString:#"The position displayed is Pondy Bazaar"];
coordsArray = malloc(sizeof(CLLocationCoordinate2D) * locations.count);
int i = 0;
for (CLLocation *loc in locations) {
coordsArray[i] = loc.coordinate;
i++;
}
routeLine = [MKPolyline polylineWithCoordinates:coordsArray
count:locations.count];
[mapView addOverlay:routeLine];
self.longitude_value.text=[NSString stringWithFormat:#"%f",center.longitude];
self.latitude_value.text=[NSString stringWithFormat:#"%f",center.latitude];
}
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
float latitude = [[view annotation ] coordinate].latitude;
float longitude = [[view annotation ] coordinate].longitude;
title_value=[[view annotation] title];
NSString * subtitle_val =[[view annotation] subtitle];
title_para.text=[NSString stringWithFormat:#"%#, %#",title_value,subtitle_val];
latitude_value.text=[NSString stringWithFormat:#"%f",latitude];
longitude_value.text=[NSString stringWithFormat:#"%f",longitude];
}
//Annotation view: icons
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id < MKAnnotation >)annotation
{
pinView = nil;
static NSString *identifier = #"Wifintech";
pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if ( pinView == nil )
pinView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:identifier];
if([[annotation title] isEqualToString:#"Wifin Technology"])
{
pinView.image = [UIImage imageNamed:#"car-side.png"];
pinView.canShowCallout = YES;
}
else if ([[annotation title] isEqualToString:#"The Residency Towers"])
{
pinView.image=[UIImage imageNamed:#"lorry-side.png"];
pinView.canShowCallout = YES;
}
else if([[annotation title] isEqualToString:#"Rado Cool Zone"])
{
//pinView.pinColor = MKPinAnnotationColorGreen;
pinView.canShowCallout = YES;
//pinView.animatesDrop = YES;
pinView.image = [UIImage imageNamed:#"car.png"];
}
else {
NSLog(#"Nothing");
}
return pinView;
}
#end

custom data variables in mkannotation

I have a map view controller (UIViewController, MKMapView), with its delegate (HCIResultMapViewController).
I wish to have following functionality in this part.
1). I wish to use my custom made NSObject , so that I can associate others details along with the basic entities like title, subtitle etc.
Hence according to my needs I coded as following
In HCIResultMapViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
_houseList = [[_resultList objectForKey:#"result"] objectForKey:#"listings"];
// NSLog([_houseList description]);
int i = 0;
for (NSDictionary *house in _houseList) {
HCIAnnotationViewController *annotation = [[HCIAnnotationViewController alloc]
initwithHouse:house];
[_mapView addAnnotation:annotation];
// NSLog(#"asdjhasdjsajdhaksdjghasdasdjahsdahskvdka");
self.currIdentifier = i;
i++;
}
[_mapView setShowsUserLocation:NO];
}
The other delegate functions
-(void) mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
for (NSObject<MKAnnotation> *annotation in _mapView.selectedAnnotations) {
NSLog(#"hellomaster");
NSLog(annotation.title);
if ([annotation isKindOfClass:[HCIAnnotationViewController class]]) {
NSLog(#"hellomaster");
}
}
The last one
-(MKAnnotationView*) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
NSString *identifier = #"currIdentifier";
MKPinAnnotationView *annotationView =
(MKPinAnnotationView *)[_mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation
reuseIdentifier:identifier];
} else {
annotationView.annotation = annotation;
}
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
annotationView.tag = self.currIdentifier;
// Create a UIButton object to add on the
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton setTitle:annotation.title forState:UIControlStateNormal];
[annotationView setRightCalloutAccessoryView:rightButton];
/*
UIButton *leftButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
[leftButton setTitle:annotation.title forState:UIControlStateNormal];
[annotationView setLeftCalloutAccessoryView:leftButton];
*/
return annotationView;
}
But I see that the class equivalence fails. Can you tell me what I'm doing wrong?
I think what I want, in simple words, is, how can I send some data (NSDictionary*) along with a annotation such that I can retrieve it whenever I want?
Please dont tag this as repeated question or so. I have tried many questions, googling etc. but couldn't find a suitable solution for this.
Here You can also set NSMutableDictionary instand of NSString.
Create custom AnnotationView:
#import <MapKit/MapKit.h>
#interface AnnotationView : MKPlacemark
#property (nonatomic, readwrite, assign) CLLocationCoordinate2D coordinate;
#property (nonatomic, strong) NSString *title; //Here You cam set `NSMutableDictionary` instand of `NSString`
#property (nonatomic, strong) NSString *subtitle; //Here You cam set `NSMutableDictionary` instand of `NSString`
#end
And in .m file
#import "AnnotationView.h"
#implementation AnnotationView
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate addressDictionary:(NSDictionary *)addressDictionary
{
if ((self = [super initWithCoordinate:coordinate addressDictionary:addressDictionary]))
{
self.coordinate = coordinate;
}
return self;
}
#end
// Use Annotation Add #import "AnnotationView.h" in your relevant .m file:
CLLocationCoordinate2D pCoordinate ;
pCoordinate.latitude = LatValue;
pCoordinate.longitude = LanValue;
// Create Obj Of AnnotationView class
AnnotationView *annotation = [[AnnotationView alloc] initWithCoordinate:pCoordinate addressDictionary:nil] ;
annotation.title = #"I m Here";
annotation.subtitle = #"This is Sub Tiitle";
[self.mapView addAnnotation:annotation];
I couldn't find exact way to implement custom data variables or the reason why my class equivalence fails. But I figured out a way to over come this kind of situation. This might be redundant in method, but still works like a charm.
So the idea is to use the tagging system in control button. I observed that every time I send a message to my map view to add a annotation it immediately calls my viewforannotation method. Since I'm iterating through an array, I maintained a index global pointer, with which I set tag of the control button. Later when the button is clicked, I retrieve the tag and use it to get the object I want.
Anyone else have any alternative or direct solution, please do post.

Resources