Actually I'm developing something in my app which displays annotations within my MKMapView and those annotations should be clickable and pass a param to a method.
The problem is for now how to pass the parameter to the next function when tapping the disclosure button.
For the formatting of the annotations I use the - (MKAnnotationView *)mapView:(MKMapView *)aMapView viewForAnnotation:(id <MKAnnotation>)annotation method to display the disclosure button. But the problem is, how to get values out of the Annotation object which is displayed at the moment?
My code for the Annotation (only the relevant part):
#synthesize coordinate, id, title, subtitle;
+ (id)annotationWithCoordinate:(CLLocationCoordinate2D)coordinate {
return [[[[self class] alloc] initWithCoordinate:coordinate] autorelease];
}
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate {
self = [super init];
if(nil != self) {
self.coordinate = coordinate;
}
return self;
}
And when touching the disclosure button the method - (void)displayInfo:(NSNumber *) anId; should be called with handing over Annotation.id as the anId param.
The disclosure button is generated with that code:
UIButton *disclosureButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
dropPin.rightCalloutAccessoryView = disclosureButton;
dropPin.animatesDrop = YES;
dropPin.canShowCallout = YES;
So - the question:
How can I pass that param to the function OR read the id of the clicked Annotation?
I thought I could do the handling of the tap on disclosureButton either with a selector for UIButton (but - how to pass the id?) or with - (void)mapView:(MKMapView *)mapView
annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control (again - how to pass the id?).
I hope you know what I mean and very big thanks in advance for your answers! :)
In calloutAccessoryControlTapped the annotation is available via the view:
MKAnnotation *annotation = view.annotation;
Related
I added an MKAnnotationView on an MKMapView, when I long press on it, I hope it can be lifted and move to another location.
How to? Special thanks!
For iOS 4.0 or later
From Apple's Location Awareness Guide > Marking Your Annotation View as Draggable:
To implement minimal support for dragging, you must do the following:
In your annotation objects, implement the setCoordinate: method to allow the map view to update the annotation’s coordinate point.
When creating your annotation view, set its draggable property to YES.
Implementing setCoordinate:
#import <MapKit/MapKit.h>
#interface mapAnnotation : NSObject<MKAnnotation> {
double latitude, longitude;
}
#end
#implementation mapAnnotation
- (void)setCoordinate:(CLLocationCoordinate2D)coordinate{
latitude = coordinate.latitude;
longitude = coordinate.longitude;
}
-(CLLocationCoordinate2D)coordinate {
CLLocationCoordinate2D coordinate = {latitude,longitude};
return coordinate;
}
// ...
#end
Creating the annotation view in the view controller:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
static NSString * const identifier = "identifier";
MKPinAnnotationView *pinView = (MKPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (pinView ==nil) {
pinView = [[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:identifier];
pinView.animatesDrop = YES;
pinView.draggable = YES;
}
return pinView;
}
For any iOS version
Track the user touches and move the pin view accordingly. Look at DDAnnotationView at the bottom of this page: http://digdog.tumblr.com/post/252784277/mapkit-annotation-drag-and-drop-with-callout-info
When the user stops dragging convert the view position to map coordinates and change the annotation coordinates. Throw in some animations for the pin.
Here is a working example : https://github.com/digdog/MapKitDragAndDrop Try adding the classes DDAnnotationView and DDAnnotation to your project, and using the MKMapViewDelegate methods below this line.
How do I keep data associated with an MKAnnotation object after the user taps the pin, sees a callout, and taps the disclosure button which opens a detailed view controller? I want to display all data associated with the pin in the detail view controller.
I have a simple MKAnnotation class that looks like:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface VoiceMemoryAnnotation : NSObject <MKAnnotation> {
NSString * blobkey;
}
#property (nonatomic, retain) NSString * blobkey;
-(id)initWithBlobkey:(NSString *) key andCoordinate:(CLLocationCoordinate2D) c;
#end
I implemented the call back "viewForAnnotation"
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKPinAnnotationView*singleAnnotationView = [[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:nil];
// PM: this pin will have a callout (i.e. dont' forget to override title function! Else exception thrown)
singleAnnotationView.canShowCallout = YES;
// PM: add disclosure button
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
// PM: when user taps disclosure button, bring them to another page with details about the selected voice memory
[rightButton addTarget:self action:#selector(showPinDetails:) forControlEvents:UIControlEventTouchUpInside];
singleAnnotationView.rightCalloutAccessoryView = rightButton;
return singleAnnotationView;
}
If I understand correctly, the above method is called when you add a VoiceMemoryAnnotation to a map object. When this viewForAnnotation is called, I simply allocate a MKPinAnnotationView object and return it. When the user taps this retuned pin, they see the callout. As soon as they click the disclosure button it calls "showPinDetails":
- (void)showPinDetails:(id)sender
{
detailViewController = [[MemoryDetailViewController alloc]initWithNibName:#"MemoryDetailViewController" bundle:nil];
[self presentModalViewController:detailViewController animated:YES];
}
The problem is the "sender" object does not contain any information about which pin was selected. Is there some way I can pass in the selected annotation to the showPinDetails method?
In the showPinDetails: method, you can get the currently selected annotation from the map view's selectedAnnotations property.
That property is an NSArray but since the map view only allows one annotation to be selected at a time, you would just use the object at index 0. For example:
- (void)showPinDetails:(id)sender
{
if (mapView.selectedAnnotations.count == 0)
{
//no annotation is currently selected
return;
}
id<MKAnnotation> selectedAnn = [mapView.selectedAnnotations objectAtIndex:0];
if ([selectedAnn isKindOfClass[VoiceMemoryAnnotation class]])
{
VoiceMemoryAnnotation *vma = (VoiceMemoryAnnotation *)selectedAnn;
NSLog(#"selected VMA = %#, blobkey=%#", vma, vma.blobkey);
}
else
{
NSLog(#"selected annotation (not a VMA) = %#", selectedAnn);
}
detailViewController = [[MemoryDetailViewController alloc]initWithNibName:#"MemoryDetailViewController" bundle:nil];
[self presentModalViewController:detailViewController animated:YES];
}
Instead of using a custom button action method, it can be easier to use the map view's calloutAccessoryControlTapped delegate method which lets you get access to the selected annotation more directly. In viewForAnnotation, remove the addTarget and just implement the delegate method:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control
{
id<MKAnnotation> selectedAnn = view.annotation;
if ([selectedAnn isKindOfClass[VoiceMemoryAnnotation class]])
{
VoiceMemoryAnnotation *vma = (VoiceMemoryAnnotation *)selectedAnn;
NSLog(#"selected VMA = %#, blobkey=%#", vma, vma.blobkey);
}
else
{
NSLog(#"selected annotation (not a VMA) = %#", selectedAnn);
}
//do something with the selected annotation...
}
I have a map with a disclosure button on the callout of it's annotations, which when clicked displays a detail view. Each point on the map needs to load up different data in the detail view - I dont want to build separate views for each annotation, for obvious reasons, but I am unsure of the best way to do this.
How do I flag which annotation has been clicked, so I can load the correct data through the detail's view controller?
You can implement the data loading function in your annotation class. To do that you will need to have a reference to that data in your annotation class.
Your annotation class can be something like
#interface MyAnnotation : NSObject<MKAnnotation> {
CLLocationCoordinate2D coordinate;
MyDataObject *mydataObject;
}
- (id) initWithCoordinate:(CLLocoationCoordinate2D)coord withMyData:(MyDataObject*)aDataObject;
- (void) loadDetailView; // this function will load the data in myDataObject to the DetailView
To get the callout touch, you can implement calloutAccessaryControlTapped function in the controller where you implement MKMapViewDelegate.
The function will be something like
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
MyAnnotation *myAnnotation = (MyAnnotation*)view.annotation;
[myAnnotation loadDetailView];
}
Edit
The implementation of initWithCoordinate will be something like:
#implementation MyAnnotation
....
- (id) initWithCoordinate:(CLLocoationCoordinate2D)coord withMyData:(MyDataObject*)aDataObject{
coordinate = coord; // cannot use "self.coordinate" here as CLLocationCoordinate2D is double instead of object (?)
self.title = <# some name #> // assign some string to the title property
self.mydataObject = aDataObject; // this is your data object
return self;
}
....
Wanted some help with a problem with mapkit I am facing. Should be a silly problem or I have missed out something while going through the mapkit framework.
Here is the senario.
I am placing multiple annotation on the map when the user performs some search like pizza.
Added button for the right annotation view, on click which opens a next detail view. The problem is how to send some information to the next view, for example I add index to annotations while creating them, now I want to access this information from annotation, and pass it to the next view via the selector set on the button.
I have checked all the mapkit delicate, but don't find a one where I can map this information with the next view and annotation.
Hope I have not confused you guys in my question. Please let me know I will reframe it.
Thaking in advance.
When you create the UIButton for the annotation, set the tag property (tag is an NSInteger property of UIView) to an id or array index that identifies the relevant object. You can then retrieve that tag value from the sender parameter to your selector.
Edit: here's some sample code.
You create your annotation view and associate the button in your delegate's -mapView:viewForAnnotation: method:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
// Boilerplate pin annotation code
MKPinAnnotationView *pin = (MKPinAnnotationView *) [self.map dequeueReusableAnnotationViewWithIdentifier: #"restMap"];
if (pin == nil) {
pin = [[[MKPinAnnotationView alloc] initWithAnnotation: annotation reuseIdentifier: #"restMap"] autorelease];
} else {
pin.annotation = annotation;
}
pin.pinColor = MKPinAnnotationColorRed
pin.canShowCallout = YES;
pin.animatesDrop = NO;
// now we'll add the right callout button
UIButton *detailButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
// customize this line to fit the structure of your code. basically
// you just need to find an integer value that matches your object in some way:
// its index in your array of MKAnnotation items, or an id of some sort, etc
//
// here I'll assume you have an annotation array that is a property of the current
// class and we just want to store the index of this annotation.
NSInteger annotationValue = [self.annotations indexOfObject:annotation];
// set the tag property of the button to the index
detailButton.tag = annotationValue;
// tell the button what to do when it gets touched
[detailButton addTarget:self action:#selector(showDetailView:) forControlEvents:UIControlEventTouchUpInside];
pin.rightCalloutAccessoryView = detailButton;
return pin;
}
Then in your action method, you'll unpack the value from tag and use it to display the right detail:
-(IBAction)showDetailView:(UIView*)sender {
// get the tag value from the sender
NSInteger selectedIndex = sender.tag;
MyAnnotationObject *selectedObject = [self.annotations objectAtIndex:selectedIndex];
// now you know which detail view you want to show; the code that follows
// depends on the structure of your app, but probably looks like:
MyDetailViewController *detailView = [[MyDetailViewController alloc] initWithNibName...];
detailView.detailObject = selectedObject;
[[self navigationController] pushViewController:detailView animated:YES];
[detailView release];
}
In the Annotation view, is it possible to grab,say, the Title or Subtitle or any other information you used while creating pins? What i am looking to do is have a certain image popup in the annotation based on one of those variables.
#import "MapPin.h"
#implementation MapPin
#synthesize coordinate;
#synthesize title;
#synthesize subtitle;
#synthesize indexnumber;
#synthesize imageFile;
-(id)initWithCoordinates:(CLLocationCoordinate2D)location
placeName: placeName
description:description
indexnum:indexnum
imageFileLoc:imageFileLoc{
self = [super init];
if (self != nil) {
imageFile=imageFileLoc;
[imageFile retain];
indexnumber=indexnum;
[indexnumber retain];
coordinate = location;
title = placeName;
[title retain];
subtitle = description;
[subtitle retain];
}
return self;
}
-(void)addAnnotations {
// Normally read the data for these from the file system or a Web service
CLLocationCoordinate2D coordinate = {35.9077803, -79.0454936};
MapPin *pin = [[MapPin alloc]initWithCoordinates:coordinate
placeName:#"Keenan Stadium"
description:#"Tar Heel Football"
indexnum:#"1"
imageFileLoc:#"owl.jpg"];
[self.map addAnnotation:pin];
Another option:
You can implement these methods:
- (void)mapView:(MKMapView *)mapView didSelectAnnotation:(MKAnnotationView *)view;
- (void)mapView:(MKMapView *)mapView didDeselectAnnotation:(MKAnnotationView *)view;
I'm trying setup a map that displays different pin colors depending on the type/class of the location in question. I know this is a pretty common thing to do, but I'm having trouble getting the viewForAnnotation delegate to consistently update/set the pin color.
I have a showThisLocation function that basically cycles through a list of AddressAnnotations and then based on the annotation class (bus stop, hospital, etc.) I set an
if( myClass == 1){
[defaults setObject:#"1" forKey:#"currPinColor"];
[defaults synchronize];
NSLog(#"Should be %#!", [defaults objectForKey:#"currPinColor"]);
} else if( myClass ==2 ){
[defaults setObject:#"2" forKey:#"currPinColor"];
[defaults synchronize];
NSLog(#"Should be %#!", [defaults objectForKey:#"currPinColor"]);
}
[_mapView addAnnotation:myCurrentAnnotation];
then my viewForAnnotation delegate looks like this,
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if( annotation == mapView.userLocation ){ return nil; }
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
MKPinAnnotationView *annView = nil;
annView = (MKPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:#"currentloc"];
if( annView == nil ){
annView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"currentloc"];
}
annView.pinColor = [defaults integerForKey:#"currPinColor"];
NSLog(#"Pin color: %d", [defaults integerForKey:#"currPinColor"]);
annView.animatesDrop=TRUE;
annView.canShowCallout = YES;
annView.calloutOffset = CGPointMake(-5, 5);
return annView;
}
The problem is that, although the NSLog statements in the "if" block always confirm that the color has been set, the delegate sometimes but not always ends up with the correct color. I've also noticed that what generally happens is that the first search for a new location will set all pins to the last color in the "if" block, but search for the same location again will set the pins to the correct color.
I suspect I am not supposed to usen NSUserDefaults in this way, but I also tried to create my own subclass for MKAnnotation which included an additional property "currentPinColor", and while this allowed me to set the "currentPinColor", when I tried to access the "currentPinColor from the delegate method, the compiler complained that it didn't know anything about "currentPinColor in connection with MKAnnotation. Fair enough I guess, but then I tried to revise the delegate method,
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MyCustomMKAnnotation>)annotation
instead of the default
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
at which point the compiler complained that it didn't know anything about the protocol for MyCustomMKAnnotation in this delegate context.
What is the proper way to set the delegate method and/or MyCustomMKAnnotation, or what is the appropriate way to achieve consistent pinColor settings. I'm just about out of ideas for things to try here.
I solved my own problem. Sometimes just formulating the question is enough!
The issue was the way I was trying to access my MKAnnotation delegate.
After posting I happened on these two related posts,
MKPinAnnotationView: Are there more than three colors available?
Mapkit issue in finding annotation current position
The answer to the first post, by user Cannonade, suggests
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation
{
// ... get the annotation delegate and allocate the MKAnnotationView (annView)
if ([annotationDelegate.type localizedCaseInsensitiveCompare:#"NeedsBluePin"] == NSOrderedSame)
{
UIImage * image = [UIImage imageNamed:#"blue_pin.png"];
UIImageView *imageView = [[[UIImageView alloc] initWithImage:image] autorelease];
[annView addSubview:imageView];
}
// ...
however my issue was that I didn't know how to the get the delegate and it was floating around somewhere in the ... ellipsis part of the above snippet. The answer to the second post, also by user Cannonade, filled this missing bit in,
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
AnnotationDelegate * delegate = (AnnotationDelegate*)view.annotation;
// do stuff with delegate.position;
}
Putting these two snippets together produced the desired result and finally solved my problem.
The working viewForAnnotation delegate method then looks like,
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if( annotation == mapView.userLocation ){ return nil; }
AddressAnnotation *delegate = annotation; //THIS CAST WAS WHAT WAS MISSING!
MKPinAnnotationView *annView = nil;
annView = (MKPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:#"currentloc"];
if( annView == nil ){
annView = [[MKPinAnnotationView alloc] initWithAnnotation:delegate reuseIdentifier:#"currentloc"];
}
if ([delegate.classPinColor localizedCaseInsensitiveCompare:#"green"] == NSOrderedSame) {
annView.pinColor = MKPinAnnotationColorGreen;
}else if ([delegate.classPinColor localizedCaseInsensitiveCompare:#"purple"] == NSOrderedSame){
annView.pinColor = MKPinAnnotationColorPurple;
}else if ([delegate.classPinColor localizedCaseInsensitiveCompare:#"red"] == NSOrderedSame){
annView.pinColor = MKPinAnnotationColorRed;
}
annView.animatesDrop=TRUE;
annView.canShowCallout = YES;
annView.calloutOffset = CGPointMake(-5, 5);
return annView;
}
Hopefully this will be of help to someone else.
Thanks Cannonade!