How to make custom MKAnnotation draggable - ios

I need MKAnnotation with different pin image.
So I created following:
#interface NavigationAnnotation : NSObject <MKAnnotation>
- (id)initWithName:(NSString*)name address:(NSString*)address coordinate:(CLLocationCoordinate2D)coordinate;
...
#interface NavigationAnnotation ()
#property (nonatomic, copy) NSString *name;
#property (nonatomic, copy) NSString *address;
#property (nonatomic, assign) CLLocationCoordinate2D theCoordinate;
#end
#implementation NavigationAnnotation
- (id)initWithName:(NSString*)name address:(NSString*)address coordinate:(CLLocationCoordinate2D)coordinate {
if ((self = [super init])) {
if ([name isKindOfClass:[NSString class]]) {
self.name = name;
} else {
self.name = #"Unknown charge";
}
self.address = address;
self.theCoordinate = coordinate;
}
return self;
}
- (NSString *)title {
return _name;
}
- (NSString *)subtitle {
return _address;
}
- (CLLocationCoordinate2D)coordinate {
return _theCoordinate;
}
And add it like this:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
static NSString *identifier_OrderAnnotation = #"NavigateAnnotation";
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
if ([annotation isKindOfClass:[NavigationAnnotation class]]) {
MKAnnotationView *annotationView = (MKAnnotationView *) [_mapView dequeueReusableAnnotationViewWithIdentifier:identifier_OrderAnnotation];
if (annotationView == nil) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier_OrderAnnotation];
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
annotationView.draggable = YES;
annotationView.image = [UIImage imageNamed:#"myimage.png"];
} else {
annotationView.annotation = annotation;
}
return annotationView;
}
}
Annotation shows on map fine but it is not draggable from some reason :(

As already mentioned, for an annotation to be draggable, it must implement a setCoordinate: method.
Additionally, since iOS 7, you may also need to implement the mapView:annotationView:didChangeDragState: method (see Draggable Pin does not have a fixed position on map and iOS MapKit dragged annotations (MKAnnotationView) no longer pan with map).
You can either implement the setCoordinate: method explicitly yourself or just declare a writeable coordinate property (named exactly like that) and synthesize it (and the getter and setter methods will be automatically implemented for you).
(Note that if you use the pre-defined MKPointAnnotation class, you don't need to do this because that class already implements a settable coordinate property.)
In your NavigationAnnotation class, to implement the explicit, manual solution to work with the existing theCoordinate property, just add the setCoordinate: method to your class implementation (keep the existing getter method):
-(void)setCoordinate:(CLLocationCoordinate2D)newCoordinate
{
self.theCoordinate = newCoordinate;
}
You may also need to implement the didChangeDragState: method in the class that implements the map view delegate (the same one that has the viewForAnnotation method) otherwise after dragging, the annotation view will hover in-place above the map even while it is panned or zoomed underneath. An example implementation of the method as given by Chris K. in his answer:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)annotationView didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState
{
if (newState == MKAnnotationViewDragStateStarting)
{
annotationView.dragState = MKAnnotationViewDragStateDragging;
}
else if (newState == MKAnnotationViewDragStateEnding || newState == MKAnnotationViewDragStateCanceling)
{
annotationView.dragState = MKAnnotationViewDragStateNone;
}
}

Just went through this issue...
After some struggling, reason was different (title, coordinate were ok), so adding possible cause here
In my annotation view, I did override - (void) setSelected:(BOOL)selected
Without calling [super setSelected:selected];
This prevented the dragging from occuring...

Related

Remove all the annotation pin except one

I have a cluster of annotation pins in map view. When I clicked on the pin I get the index of that pin. I want that if I click on the pin than all the pins are hide except that on which user clicked and if again I click on that pin all the pins are shown.
Here is the code in which I got the index of the selected pin.
CPointAnnotation *cAnno=(CPointAnnotation*)view.annotation;
NSInteger index=cAnno.index;
if (index<hospitalsArry.count) {
selectedHospital=[hospitalsArry objectAtIndex:index];
if (selectedIndex==index) {
selectedIndex=-1;
return;
}else{
selectedIndex=index;
[[self.mapView viewForAnnotation:cAnno] setHidden:NO];
}
CustomAnnotation.h
#import <MapKit/MapKit.h>
#interface CustomAnnotation : NSObject <MKAnnotation>
#property (nonatomic,readonly)CLLocationCoordinate2D coordinate;
#property (nonatomic, copy)NSString *title;
#property (nonatomic, strong)MKAnnotationView *annotaitonView;
-(id)initWithTitle:(NSString *)newTitle coordinates:(CLLocationCoordinate2D)newCoordinate;
-(MKAnnotationView *)createAnnotationView;
#end
CustomAnnotation.m
#implementation CustomAnnotation
-(id)initWithTitle:(NSString *)newTitle coordinates:(CLLocationCoordinate2D)newCoordinate
{
if (self = [super init]) {
_title = newTitle;
_coordinate = newCoordinate;
}
return self;
}
-(MKAnnotationView *)createAnnotationView
{
MKAnnotationView *annView=[[MKAnnotationView alloc]initWithAnnotation:self reuseIdentifier:#"MyCustomAnnoation"];
annView.enabled=TRUE;
annView.canShowCallout=TRUE;
annView.image=[UIImage imageNamed:#"map-pin-marker-circle-128.png"];
return annView;
}
#end
in MapViewController.m
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKPointAnnotation class]]) {
return nil;
}
if ([annotation isKindOfClass:[CustomAnnotation class]]) {
CustomAnnotation *myAnn=(CustomAnnotation *)annotation;
MKAnnotationView *annView=[mapView dequeueReusableAnnotationViewWithIdentifier:#"MyCustomAnnoation"];
if (annView == nil) {
annView=[myAnn createAnnotationView];
}
else
{
annView.annotation=myAnn;
}
myAnn.annotaitonView=annView;
return annView;
}
else
return nil;
}
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
[self hideOtherPinsByIgnoringThis:view];
}
-(void)hideOtherPinsByIgnoringThis:(MKAnnotationView *)ann
{
NSArray *arrAllPins=[self.myMapView annotations];
//Find selected Annotation View in all pins on map
NSMutableArray *removeAnn=[[NSMutableArray alloc]init];
for (CustomAnnotation *annotation in arrAllPins)
{
if (annotation.annotaitonView != ann) {
[removeAnn addObject:annotation];
}
}
[self.myMapView removeAnnotations:removeAnn];
}

Tap on Annotation on MkMapKit, return name

The problem I'm working on is:
I have a MKMapKit and whenever a user taps on a building, street, the name pops up from the mapView, like so:
I have my own class AddressAnnotation, like so:
AddressAnnotation.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface AddressAnnotation : NSObject <MKAnnotation>
- (id)initWithName:(NSString *)name address:(NSString *)address coordinate:(CLLocationCoordinate2D)coordinate;
#end
AddressAnnotation.m
#import "AddressAnnotation.h"
#import <AddressBook/AddressBook.h>
#interface AddressAnnotation()
#property (nonatomic, copy) NSString *name;
#property (nonatomic, copy) NSString *address;
#property (nonatomic, assign) CLLocationCoordinate2D theCoordinate;
#end
#implementation AddressAnnotation
- (id)initWithName:(NSString*)name address:(NSString*)address coordinate:(CLLocationCoordinate2D)coordinate {
if ((self = [super init])) {
if ([name isKindOfClass:[NSString class]]) {
self.name = name;
} else {
self.name = #"";
}
self.address = address;
self.theCoordinate = coordinate;
}
return self;
}
- (NSString *)title {
return _name;
}
- (NSString *)subtitle {
return _address;
}
- (CLLocationCoordinate2D)coordinate {
return _theCoordinate;
}
And in my main MapViewController, I can specify a point and add a pin to that location, but that isn't what I want. I just want to be able to tap on a object and have their name pop up.
I couldn't find a question similar to this; please inform me if I've duplicated a question.
Thank you.
If you want a balloon to pop up above your annotation when you tap it. You use can MKMapViewDelegate in your controller.
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
// do this so you dont run the code for any other annotation type (eg blue dot for where your location is)
if([annotation isKindOfClass:[AddressAnnotation class]]){
MKPinAnnotationView* pv = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"spot"];
pv.pinColor = MKPinAnnotationColorPurple;
// decorate the balloon
UIImageView* iv = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"something.png"]];
iv.frame = CGRectMake(0, 0, 30, 30);
pv.leftCalloutAccessoryView = iv;
pv.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
// (default title and subtitle of the balloon will be taken from the annoation object)
// allow balloon to show when tapping
pv.canShowCallout = YES;
return pv;
}
return nil;
}

Getting object from pin selection

I'm using an array of Locations (stored online) which have a LocationID, lat, long, name, PinNumber and UserId.
Steps : I load the complete locations array of the selected user
I create pins with that array ( a simple for loop that uses the name, location, etc.)
Sadly, the MKPointAnnotation can only have a name and coordinates, and this is where my problems appears.
When my user selects a pin and uses the annotation (correct me if i'm wrong, that is the little info button inside the selected pin), he is redirected to another page where he can edit that location, and I can't find it in the database because i can't get the ID of the location.
I tried using NSInteger index = [mapView.annotations indexOfObject:view.annotation]; and check that index in my location array, but it just doesn't match.
What can i do to get my object back from that pin ? Or any workaround that gets the job done really.
You can subclass MKAnnotation and add your object ID to it as follows:
In CustomAnnotation.h,
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface CustomAnnotation : NSObject <MKAnnotation>
#property (nonatomic, retain) NSString *title;
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#property (nonatomic, retain) NSNumber *objectID;
- (id)initWithTitle:(NSString *)newTitle id:(NSNumber *)objectID location:(CLLocationCoordinate2D)location;
- (MKAnnotationView *)annotationView;
#end
In CustomAnnotation.m,
#import "CustomAnnotation.h"
#implementation CustomAnnotation
- (id)initWithTitle:(NSString *)newTitle id:(NSNumber *)objectID location:(CLLocationCoordinate2D)location
{
self = [super init];
if(self)
{
// Initialization code
_title = newTitle;
_coordinate = location;
_objectID = objectID;
}
return self;
}
- (MKAnnotationView *)annotationView
{
MKAnnotationView *annotationView = [[MKAnnotationView alloc] initWithAnnotation:self reuseIdentifier:#"MyCustomAnnotation"];
// Your settings
annotationView.draggable = NO;
annotationView.enabled = YES;
return annotationView;
}
#end
Also, in mapView:viewForAnnotation:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
// Customise all annotations except MKUserLocation
if([annotation isKindOfClass:[CustomAnnotation class]])
{
CustomAnnotation *point = (CustomAnnotation *)annotation;
MKAnnotationView *pointView = (MKAnnotationView *)[self.mapView dequeueReusableAnnotationViewWithIdentifier:#"MyCustomAnnotation"];
if(pointView == nil)
pointView = point.annotationView;
else
pointView.annotation = annotation;
...
// do something with point.objectID
return pointView;
}
else
return nil;
}
You can subclass MKPointAnnotation and create a new property with your object
#interface CustomPointAnnotation : MKPointAnnotation
#property (nonatomic, strong) CustomObject* object;
#end
Now whenever you deal with an <MKAnnotation>, cast it to a CustomPointAnnotation* and set/get the object property.

MapKit Annotations not showing up on map

I'm having trouble getting the MKAnnotationViews show up on the map in MapKit. I'm using iOS 7, and have now searched the forums and the web for many hours, trying different samples and setups.
Below I have the most basic setup possible (I think) to make it work. The app contains a single ViewController with a toolbar with a button on top, and the rest is the MapView. The button triggers the method zoomToUser:(UIBarButtonItem*)sender. Right-clicking and checking my outlets and delegates seem to be correct. I have some NSLog-statements being triggered to output some debug info.
First the VC:
#import "ViewController.h"
#import "Data.h"
#interface ViewController () <MKMapViewDelegate>
#property (weak, nonatomic) IBOutlet MKMapView *mapView;
- (IBAction)zoomToUser:(UIBarButtonItem *)sender;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.mapView.showsUserLocation = YES;
Data *ann = [[Data alloc] init];
[self.mapView addAnnotation:ann];
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self zoomToUser:nil];
}
-(IBAction)zoomToUser:(UIBarButtonItem *)sender
{
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(self.mapView.userLocation.coordinate, 2000, 2000);
NSLog(#"region: %f x %f",self.mapView.userLocation.coordinate.latitude,self.mapView.userLocation.coordinate.longitude);
[self.mapView setRegion:region animated:YES ];
NSArray *arr = self.mapView.annotations;
for(int i = 0, max = arr.count; i < max; i++)
{
id<MKAnnotation> annotation = arr[i];
NSLog(#"%d of %d: %# (%#)",i+1,max,annotation.title, [annotation class]);
}
}
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation
{
NSString *reuseId = #"Test";
if([annotation isKindOfClass:[MKUserLocation class]])
{
return nil;
}
else if( [annotation isKindOfClass:[Data class]])
{
MKAnnotationView *annov = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if(!annov)
{
annov = [[MKAnnotationView alloc]initWithAnnotation:annotation
reuseIdentifier:reuseId];
}
else
{
annov.annotation = annotation;
}
annov.canShowCallout = YES;
NSLog(#"Title: %#",annotation.title);
return annov;
}
else
{
return nil;
}
}
#end
And the Data class:
#import "Data.h"
#implementation Data
-(NSString *)title
{
return #"The title";
}
-(NSString *)subtitle
{
return #"A subtitle";
}
-(CLLocationCoordinate2D)coordinate
{
CLLocationCoordinate2D coordinate;
coordinate.latitude = 60.123456;
coordinate.longitude = 10.123456;
return coordinate;
}
#end
The Data.h:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface Data : NSObject <MKAnnotation>
#end
And the ViewController.h:
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface ViewController : UIViewController
#end
As you can see the viewForAnnotation-method prints the annotations title in console when it's requested, and each time the map pans the area, it prints.
Pressing the BarButton and triggering the zoomToUser:-method logs to console that the map indeed has two annotations, the MKUserLocation and the one I added.
How come my MapView tells me it has the annotation, it asks for the view, it gets the view, but it does not show it?
In viewForAnnotation, the code is creating and returning an MKAnnotationView for annotations of type Data.
The MKAnnotationView class, by default, creates an empty view.
That is, its image property is nil and the view basically contains no content.
So, as-is, the annotation views are being created and added but they are invisible.
You can either:
Set the image property on the MKAnnotationView:
annov.image = [UIImage imageNamed:#"something"];
Or, simpler, create an MKPinAnnotationView instead which is a convenient subclass of MKAnnotationView that displays a pin image for you:
MKPinAnnotationView *annov = (MKPinAnnotationView *)[mapView dequeue...
if(!annov)
{
annov = [[MKPinAnnotationView alloc]initWithAnnotation:annotation
reuseIdentifier:reuseId];
annov.pinColor = MKPinAnnotationColorRed; //or Green or Purple
}
else
...
Unrelated:
You've created a custom annotation class Data which is fine but if all you need are the three properties title, subtitle, and coordinate, then you can just use the pre-defined MKPointAnnotation class which lets you set all the properties per-instance:
MKPointAnnotation *ann = [[MKPointAnnotation alloc] init];
ann.title = #"The title";
ann.subtitle = #"A subtitle";
ann.coordinate = CLLocationCoordinate2DMake (60.123456, 10.123456);
[self.mapView addAnnotation:ann];

MKMapView with custom MKAnnotation

I've a MKMapView.
I want to put a custom MKAnnotation on my map.
They are some restaurant places. How can I do it?
My question is how can I make a custom MKAnnotation?
Thanks, guys.
First, let's define custom as meaning not simply title and subtitle. We want to change the size of the MKAnnotation and include some custom graphics.
There are two parts to an annotation you might want to customize:
MKAnnotation
MKAnnotationView
For the most basic MKAnnotation you would simply adopt the protocol and return nil for title and subtitle, but you could also carry a lot more information in your annotation for an extended callout upon tapping an accessory indicator. You can add all of the annotations to the MKMapView using addAnnotation: in viewDidLoad for example.
MKAnnotation Header
#interface CPAnnotation : NSObject <MKAnnotation> {
#private
CLLocationCoordinate2D _coordinate;
NSString *_title;
NSString *_subtitle;
}
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#property (nonatomic, readonly, copy) NSString *title;
#property (nonatomic, readonly, copy) NSString *subtitle;
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate;
#end
MKAnnotation Implementation
#implementation CPAnnotation
#synthesize coordinate = _coordinate;
#synthesize title = _title;
#synthesize subtitle = _subtitle;
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate {
self = [super init];
if (self != nil) {
self.coordinate = coordinate;
}
return self;
}
- (NSString *)title {
return _title;
}
- (NSString *)subtitle {
return _subtitle;
}
#end
The next step is to customize the callout from the pin dropped. To do this you need to customize MKAnnotationView. According to Apple you shouldn't make a huge callout by default. They recommend a standard size callout that has a button to open a bigger one. They use the lowercase i in a blue circle icon. Those icons can be set via the view's leftCalloutAccessoryView and rightCalloutAccessoryView property. If you already adopted the MKMapViewDelegate protocol and set yourself as the MKMapView's delegate you will get the callback for viewForAnnotation:.
MKAnnotationView MKMapViewDelegate callback
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
static NSString *const kAnnotationReuseIdentifier = #"CPAnnotationView";
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:kAnnotationReuseIdentifier];
if (annotationView == nil) {
annotationView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:kAnnotationReuseIdentifier] autorelease];
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeInfoLight];
}
return annotationView;
}
You can further customize this in a custom view overriding the drawRect method, providing an image to the image property, or you could even implement an MKAnnotationView in a XIB. It is worth some experimentation.
Apple's WeatherAnnotationView Example illustrates overriding drawRect.
I had a case where I wanted something like a standard Pin annotation, but the designer wanted a custom graphic.
I wrote a subclass of MKAnnotationView to display the graphic. The only difference is that it overrides the standard class's image.
BlipAnnotationView.h
#import <MapKit/MapKit.h>
#interface BlipAnnotationView : MKAnnotationView
- (id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier;
#end
BlipAnnotationView.m
#import "BlipAnnotationView.h"
#implementation BlipAnnotationView
- (id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self) {
UIImage *blipImage = [UIImage imageNamed:#"blip.png"];
CGRect frame = [self frame];
frame.size = [blipImage size];
[self setFrame:frame];
[self setCenterOffset:CGPointMake(0.0, -7.0)];
[self setImage:blipImage];
}
return self;
}
#end
Then in the class that displays the map, I made the class implement the MKMapViewDelegate protocol. The mapView:viewForAnnotation: method creates a new instance of BlipAnnotationView if necessary.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
NSLog(#"mapView:%# viewForAnnotation:%#", mapView, annotation);
static NSString *const kAnnotationIdentifier = #"BlipMapAnnotation";
BlipAnnotationView *annotationView = (BlipAnnotationView *)
[mapView dequeueReusableAnnotationViewWithIdentifier:kAnnotationIdentifier];
if (! annotationView) {
annotationView = [[BlipAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:kAnnotationIdentifier];
}
[annotationView setAnnotation:annotation];
return annotationView;
}
Finally I set the class as the delegate of the map view in awakeFromNib:
- (void)awakeFromNib
{
[...]
[_theMapView setDelegate:self];
}
I didn't have to change the code that positioned the annotation at all:
MKPointAnnotation *annotationPoint = [[MKPointAnnotation alloc] init];
[annotationPoint setCoordinate:[userLocation coordinate]];
[annotationPoint setTitle:label];
[_theMapView addAnnotation:annotationPoint];

Resources