How to get location from map pin in iOS - ios

I am using the MKMapView object and I am able to create pins and place them on the map. What I am trying to do now is get the information back from the pin. For example in my
- (MKAnnotationView*) mapView:(MKMapView *)mapView viewForAnnotation (id<MKAnnotation>)annotation
I create my pins and set them on the map. I am also creating buttons to use when a user clicks on the pin. All that works well and good. Now my question is when that button is clicked how do I get information from that clicked pin? Below is a small sample code of how I currently have it setup. Any advice would be greatly appreciated.
- (MKAnnotationView*) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
MKPinAnnotationView *annView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"location"];
annView.pinColor = MKPinAnnotationColorRed;
annView.animatesDrop = YES;
annView.canShowCallout = YES;
annView.calloutOffset = CGPointMake(-5, 5);
UIButton* callout = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[callout addTarget:self action:#selector(loadDetails:) forControlEvents:UIControlEventTouchUpInside];
annView.rightCalloutAccessoryView = callout;
return annView;
}
- (void)loadDetails:(id)sender
{
//TODO: Get pin coordinates or address and match it back to an array of available address to show details in a new view
}
Any advice is greatly appreciated

Instead of setting the target of your callout, respond to the delegate method
mapView:annotationView:calloutAccessoryControlTapped:
The annotationView will have an annotation property with the coordinate.

You should create a class that derives from MKPinAnnotationView that has a property of id<MkAnnotationView>. During the method above, create this new object instead of the MKPinAnnotationView and set the id<MKAnnotation> property to the annotation passed in.

Related

How to identify XCode MKMapView pins from viewForAnnotation

I am working on an application with an mkmapview, which drops pins onto a mapview.
I need to be able to colour the pins based on information about the pin.
The current code that drops the map pins is:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation {
if (annotation == self.mapView.userLocation) return nil;
NSLog(#"annotation = %#", annotation);
static NSString* AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView* customPin = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
customPin.pinColor = MKPinAnnotationColorRed;
customPin.animatesDrop = YES;
customPin.canShowCallout = YES;
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
customPin.rightCalloutAccessoryView = rightButton;
return customPin;
}
If I change the line:
customPin.pinColor = MKPinAnnotationColorRed;
Then I can change the colour of ALL the dropped pins, but how can I identify which pin is being dropped, so that I can only re-colour the pin if needed?
I added the log line:
NSLog(#"annotation = %#", annotation);
But it returns, for example:
annotation = <MapAnnotation: 0x7feabd749190>
annotation = <MapAnnotation: 0x7feac04edf50>
annotation = <MapAnnotation: 0x7feabd79f860>
How can I use this to identify the pin?
Or should I be colouring the annotation pins in a different location?
You can add any object that conforms to the MKAnnotation protocol to a map as an annotation.
I suggest creating a custom annotation object that has extra properties (like an enum for the pin type, for example)
Then in your viewForAnnotation method, once you make sure it's not the user location annotation, cast the id pointer to your custom annotation object type and check your custom properties to see what type of pin to display (it could be as simple as a switch statement.)

mapView:viewForAnnotation: not always called

I am just starting out wit MKMapKit and call outs. In particular, I have started to use mapView: viewForAnnotation: and a right accessory button. My problem is that the method only get called for newly added pins but not for those previously added to the map view. The call out opens but without a right accessory. Should this method be called for all pins?
-(MKAnnotationView*)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
static NSString *resuseId = #"WaypointMapViewVC";
MKAnnotationView *view =[mapView dequeueReusableAnnotationViewWithIdentifier:resuseId];
if (!view){
view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:resuseId];
view.canShowCallout=YES;
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
view.rightCalloutAccessoryView=rightButton;
} else {
view.annotation=annotation;
}
return view;
}
Am I doing something wrong here?

Force MKMapView viewForAnnotation to update

So I have a MKMapView with all my pins added, and the colour of the pin is dependent on whether a value is set for that pin. When I first load the app, viewForAnnotation is called and the colours are set accordingly. However, when I update the pin's details (such as location, title, etc...) I also update the pinColour to find it doesn't update. It looks like viewForAnnotation isn't called again after the initial add.
I have read many questions similar to this and I can confirm that mapView.delegate = self;
Here is my viewForAnnotation code:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(MapAnnotation *)annotation
{
if([annotation class] == MKUserLocation.class)
return nil;
NSString *pinIdentifier = annotation.identifier; // This is a unique string for each pin and is getting populated every time!
MKPinAnnotationView *annotationView = (MKPinAnnotationView *) [mapView dequeueReusableAnnotationViewWithIdentifier:pinIdentifier];
if(annotationView == nil)
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:pinIdentifier];
else
annotationView.annotation = annotation; // Never had this line fire...
annotationView.canShowCallout = YES;
annotationView.animatesDrop = NO;
annotationView.enabled = YES;
annotationView.tag = annotation.counter;
if(annotation.pinColour == Stopped) // from enum
annotationView.pinColor = MKPinAnnotationColorRed;
else
annotationView.pinColor = MKPinAnnotationColorGreen;
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[infoButton addTarget:self action:#selector(mapCalloutButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
infoButton.tag = annotation.counter;
annotationView.rightCalloutAccessoryView = infoButton;
return annotationView;
}
Here is the code where I add the pin:
CLLocationCoordinate2D annotationCoord;
annotationCoord.latitude = latestPosition.latitude;
annotationCoord.longitude = latestPosition.longitude;
MapAnnotation *annotation = [[MapAnnotation alloc] init];
annotation.coordinate = annotationCoord;
annotation.identifier = theIdentifier;
annotation.title = theTitle;
annotation.subtitle = theSubtitle
annotation.pinColour = [self getPinColour];
annotation.counter = theCounter;
[theMapView addAnnotation:annotation];
Here is the code where I update the pin (different method to add):
updatePin = true;
pinCounter = mapPin.counter;
CLLocationCoordinate2D annotationCoord;
annotationCoord.latitude = latestPosition.latitude;
annotationCoord.longitude = latestPosition.longitude;
[mapPin setCoordinate:annotationCoord];
mapPin.identifier = theIdentifier;
mapPin.subtitle = theSubtitle;
mapPin.pinColour = [self getPinColour];
I'm not sure what I'm missing. viewForAnnotation is obviously working, it's just not ever called after the initial add! If it were to call this function I'm 100% sure it would work as it does the colour change if I restart the app!
EDIT: Oh and I really don't want to start removing annotations and re-adding them. It's what I'm doing in the short term anyway!
Actually, I dont' know if this worked for you but this is how I did it.
I didn't need to delete the annotation from map. All I need to do is tell the map to give me the annotation view for a parameter annotation. The map will return the correct annotation. From there, I have a property for my custom annotation to identify whether it is an active item, if yes, show the normal pin image, else show full pin image.
-(void)updateAnnotationImage:(CustomAnnotation *)paramAnnotation
{
MKAnnotationView *av = [geoMap viewForAnnotation:paramAnnotation];
if (paramAnnotation.active)
{
av.image = [UIImage imageNamed:#"PinNormal.png"];
}
else
{
av.image = [UIImage imageNamed:#"PinFull.png"];
}
}
Bit late but hopefully it helps others who came across this problem.
Due to the way the map view caches its annotations, you NEED to remove and re-add the annotation if you need to make changes to its appearance. A simple remove & add is the way to go. There is no cache invalidating mechanism but this.
I also found this answer helpful: In which case that mapView:viewForAnnotation: will be called?
Whenever you call addAnnotation method
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id < MKAnnotation >)annotation gets called.
Swift 2.1:
I had the same issue, and found a quick solution, trigger this when needed, also sending it to the main thread would be wise:
var annotationsArray = mapView.annotations
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotations(arrayIncs)
arrayIncs.removeAll()
Just spent a couple of hours to get this to work on Xamarin; this is a warning for other Xamarin developers. Make sure you use the ViewForAnnotation method and not the GetViewForAnnotation delegate. I was using the wrong method which returned new annotation views instead of the existing ones... of course it wasn't working!

How to recognize which pin was tapped

I got an app with MKMapView and large number of pins on this map.
Every pin got rightCalloutAccessoryView. I create it in this way:
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self action:#selector(showDetails:) forControlEvents:UIControlEventTouchUpInside];
pinView.rightCalloutAccessoryView = rightButton;
How should i know, which pin was tapped? Thnx
In the showDetails: method, you can get the pin tapped from the map view's selectedAnnotations array. Even though the property is an NSArray, just get the first item in the array since the map view only allows one pin to be selected at a time:
//To be safe, may want to check that array has at least one item first.
id<MKAnnotation> ann = [[mapView selectedAnnotations] objectAtIndex:0];
// OR if you have custom annotation class with other properties...
// (in this case may also want to check class of object first)
YourAnnotationClass *ann = [[mapView selectedAnnotations] objectAtIndex:0];
NSLog(#"ann.title = %#", ann.title);
By the way, instead of doing addTarget and implementing a custom method, you can use the map view's calloutAccessoryControlTapped delegate method. The annotation tapped is available in the view parameter:
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control
{
NSLog(#"ann.title = %#", view.annotation.title);
}
Make sure you remove the addTarget from viewForAnnotation if you use calloutAccessoryControlTapped.

Get URI from MKAnnotation selected on a MKmapview

This may be a design error instead of a syntax error, so let me know if I'm coding in the wrong direction here.
I'm new to Cocoa Touch/Objective-C and I've been working through tutorials on Core Data and MapKit. So far the app places an annotation on a mapview for items fetched from Core Data. I made a custom annotation object (called MapPin) that also holds a URI (NSURL*) pointing to the object in Core Data that it represents. When the user selects an annotation I want to use the URI property of that annotation to find which object in coredata the annotation represents.
This works if I add the annotation to the mapview beforehand. Here I add a MapPin annotation for each object "thing"
//viewWillAppear function in my ViewController
NSURL *uri = [[thing objectID] URIRepresentation];
MapPin *annotation = [[[MapPin alloc] initWithName:thing.common description:thing.latin coordinate:coordinate uri:uri] autorelease];
NSLog(#"MapPin URI: %#", [annotation.uri absoluteString]); //This works!
[_mapView addAnnotation:annotation];
NSLog(#"Placed Map Pin: %#", thing.common);
Later, after the user selects an annotation and clicks a button in the annotationView callout, I want to access the URI for the selected annotation
//the UIButton click action in my view controller
MSPTreesAppDelegate *del = (MSPTreesAppDelegate *)[UIApplication sharedApplication].delegate;
NSArray *annArray = _mapView.selectedAnnotations;
MapPin *selectedPin = [annArray objectAtIndex:0];
NSLog(#"Selected Pin Name: %#", selectedPin.name); //Works fine
NSLog(#"URI PASSED: %#", [selectedPin.uri absoluteString]); //Doesn't work
NSURL* uriForTree = selectedPin.uri; //also doesn't work
I notice in the debugger that before I add the annotation to the mapview, the URI property shows the correct string. After the (MapPin) annotation is selected from the map view, the URI property in the debugger just shows "invalid summary".
When I try to access the URI property the program ends and I get a "Thread 1: Program Received signal: "EXC_BAD_ACCESS"." error. The Log doesn't show anything helpful besides that.
I assume that my custom URI property for my MKAnnotation object isn't supported by the MKAnnotationView or the MKMapView, but I can't figure out where the URI is getting lost. Is there a way I can retrieve my URI property from the selected annotation? Other suggestions for methods to accomplish the same concept are welcome.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
static NSString *identifier = #"MapPin";
if ([annotation isKindOfClass:[MapPin class]]) {
NSLog(#"Annotation is a MapPin");
TreeAnnotationView *annotationView = (TreeAnnotationView *) [_mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[TreeAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
} else {
annotationView.annotation = annotation;
}
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
//Place details button in callout
UIButton * detailButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[detailButton setTitle:annotation.title forState:UIControlStateNormal];
[detailButton addTarget:self action:#selector(showDetails:) forControlEvents:UIControlEventTouchUpInside];
annotationView.rightCalloutAccessoryView = detailButton;
return annotationView;
}
return nil;
}
Why not make your MapPin be a subclass of NSManagedObject and have it implement MKAnnotation?
#interface MapPin : NSManagedObject <MKAnnotation>
#end
That way you one and only one model object to represent the pin and that model object is persisted through CoreData.
But your "EXC_BAD_ACCESS" and "invalid summary" indicate that the URI isn't' being retained (strong if you're using ARC).

Resources