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.
Related
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;
}
I am trying to use a map to set annotation in it, where the user wants to put it.
I want the user to touch the screen where he wants to put a pin, and by clicking on the appeared pin, he can be redirected to another view where he can put details.
I followed several tutorials to make the button appear on the right of the callout, but this does not work...
Here is my code :
MAPpin is the NSObject file :
MAPpin.h :
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface MAPpin : NSObject <MKAnnotation> {
CLLocationCoordinate2D coordinate;
NSString *title;
NSString *subtitle;
}
#property (weak, nonatomic) IBOutlet MKMapView *mapView;
#property (nonatomic, assign) CLLocationCoordinate2D coordinate;
#property (nonatomic, copy) NSString *title;
#property (nonatomic, copy) NSString *subtitle;
#end
MAPpin.m :
#import "MAPpin.h"
#implementation MAPpin
#synthesize coordinate,title,subtitle, mapView;
#end
And my view controller :
MAPwelcomeViewController.h
#import <UIKit/UIKit.h>
#import <MAPKit/MKAnnotation.h>
#import "MAPAppDelegate.h"
#import "MAPpin.h"
#interface MAPwelcomeViewController : UIViewController <MKMapViewDelegate>
- (IBAction)longpress:(UILongPressGestureRecognizer *)sender;
#property (weak, nonatomic) IBOutlet MKMapView *mapView;
#end
and the MAPwelcomeViewController.m :
#import "MAPwelcomeViewController.h"
#import "MAPpin.h"
#import "myAnnotation.h"
#define METERS_PER_MILE 1609.344
#interface MAPwelcomeViewController ()
#end
#implementation MAPwelcomeViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
{
[super viewDidLoad];
_mapView.showsUserLocation = YES;
}
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//1
CLLocationCoordinate2D zoomLocation;
zoomLocation.latitude = 40.740848;
zoomLocation.longitude= -73.991145;
// 2
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, 0.3*METERS_PER_MILE, 0.3*METERS_PER_MILE);
[self.mapView setRegion:viewRegion animated:YES];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(userLocation.coordinate, 800, 800);
[self.mapView setRegion:[self.mapView regionThatFits:region] animated:YES];
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#pragma mark -MapView Delegate Methods
//6
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
//7
if([annotation isKindOfClass:[MKUserLocation class]])
return nil;
//8
static NSString *identifier = #"myAnnotation";
MKPinAnnotationView * annotationView = (MKPinAnnotationView*)[self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (!annotationView)
{
//9
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
annotationView.pinColor = MKPinAnnotationColorPurple;
annotationView.animatesDrop = YES;
annotationView.canShowCallout = YES;
}else {
annotationView.annotation = annotation;
}
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
return annotationView;
}
- (IBAction)longpress:(UILongPressGestureRecognizer *)sender {
CGPoint point = [sender locationInView:self.mapView];
CLLocationCoordinate2D loccoord = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
myAnnotation *ann = [[myAnnotation alloc] initWithCoordinate:loccoord title:#"Test"];
[self.mapView addAnnotation:ann];
}
#end
So following (http://www.codigator.com/tutorials/mapkit-tutorial-for-ios-beginners/"this links") I added the following method :
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
But when I run the app, no button is showing... what do you think I am doing wrong ?
Thanks in advance,
regards !
Most likely, the map view's delegate is not set so the viewForAnnotation delegate method never gets called and the map view creates a default red pin with no callout accessory button.
In the storyboard, right-click on the map view control and connect its delegate outlet to the View Controller.
Or, in code, in the viewDidLoad method, before setting showsUserLocation, set the delegate programmatically:
self.mapView.delegate = self;
Some unrelated comments...
Once the button is appearing, to handle the user tapping the callout accessory, a recommended approach is to implement the calloutAccessoryControlTapped delegate method. See the answer to MKAnnotationView Push to View Controller when DetailDesclosure Button is Clicked for an example.
A separate issue I should point out is that the gesture handler method isn't checking the gesture recognizer's state before creating an annotation. So what can happen is multiple pins can get created while the user is still doing a single long press. To avoid this, add a check like this at the top of the longpress: method:
if (sender.state != UIGestureRecognizerStateBegan)
{
return;
}
Another unrelated thing is that the code is creating an annotation of type myAnnotation by doing myAnnotation alloc but the annotation class you've shown is of type MAPpin.
Finally, adding an MKMapView as an IBOutlet and property in the MAPpin class is completely unnecessary. You should remove it from there before it leads to confusion.
I am trying to create draggable custom pin with Title popup with MapKit. but when I implement viewForAnnotation to make PIN appear as custom image, its disappears pop title as if I youch Pin on map it does not show titla popup,
while if I comments viewForAnnotation it works fine, but I want both things custom image and popup title when tap pin.
below is code
- (void)viewDidLoad
{
MKCoordinateRegion newRegion = MKCoordinateRegionMakeWithDistance(LocCoordinate, 20000, 20000);
DDAnnotation *point = [[DDAnnotation alloc] init];
[point setCoordinate:mapCenter];
[point setTitle:#"Where am I"];
[point setSubtitle:#"I am here"];
[self.mvTripMap addAnnotation:point];
[self.mvTripMap setRegion:newRegion animated:YES];
[super viewDidLoad];
}
-(MKAnnotationView *)mapView:(MKMapView *)MapView viewForAnnotation:(id<MKAnnotation>)annotation{
static NSString *cabAnnotationIdentifier=#"cabAnnotationIdentifier";
MKAnnotationView *annotationView=[MapView dequeueReusableAnnotationViewWithIdentifier:cabAnnotationIdentifier];
if(!annotationView){
annotationView=[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:cabAnnotationIdentifier];
annotationView.image=[UIImage imageNamed:#"start.png"];
annotationView.draggable = YES;
}
return annotationView;
}
DDAnnotation.h
#import <MapKit/MapKit.h>
#interface DDAnnotation : MKPlacemark {
CLLocationCoordinate2D coordinate_;
NSString *title_;
NSString *subtitle_;
}
// Re-declare MKAnnotation's readonly property 'coordinate' to readwrite.
#property (nonatomic, readwrite, assign) CLLocationCoordinate2D coordinate;
#property (nonatomic, retain) NSString *title;
#property (nonatomic, retain) NSString *subtitle;
#end
DDAnnotation.m
#import "DDAnnotation.h"
#implementation DDAnnotation
#synthesize coordinate = coordinate_;
#synthesize title = title_;
#synthesize subtitle = subtitle_;
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate addressDictionary:(NSDictionary *)addressDictionary {
if ((self = [super initWithCoordinate:coordinate addressDictionary:addressDictionary])) {
self.coordinate = coordinate;
}
return self;
}
#end
Just put annotationView.canShowCallout= YES; and it will work
updated code
-(MKAnnotationView *)mapView:(MKMapView *)MapView viewForAnnotation:(id<MKAnnotation>)annotation{
static NSString *cabAnnotationIdentifier=#"cabAnnotationIdentifier";
MKAnnotationView *annotationView=[MapView dequeueReusableAnnotationViewWithIdentifier:cabAnnotationIdentifier];
if(!annotationView){
annotationView=[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:cabAnnotationIdentifier];
annotationView.image=[UIImage imageNamed:#"start.png"];
annotationView.draggable = YES;
annotationView.canShowCallout= YES;
}
return annotationView;
}
I would like to have a MKMapView showing annotations with disclosure-buttons which lead to a view controller like the Golden Gate Bridge annotation in this Apple sample app.
I load the coordinates from a plist and the annotations appear correctly with title/subtitle but the method
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
has no effect.
I guess that I somehow have to link the annotations with the pinannotations?
MapViewController.h:
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#import "Annotation.h"
#interface MapViewController : UIViewController<CLLocationManagerDelegate, MKMapViewDelegate>
#property (strong, nonatomic) CLLocationManager *location;
#property (nonatomic, retain) NSArray *data;
#end
MapViewController.m:
#import "MapViewController.h"
#interface MapViewController ()
#property (nonatomic, weak) IBOutlet MKMapView *mapView;
#end
#implementation MapViewController
#synthesize data;
#synthesize location, minLatitude, maxLatitude, minLongitude, maxLongitude;
- (void)viewDidLoad
{
NSString *dataPath = [[NSBundle mainBundle] pathForResource:#"City" ofType:#"plist"];
self.data = [NSArray arrayWithContentsOfFile:dataPath];
for (int i = 0; i < data.count; i++) {
NSDictionary *dataItem = [data objectAtIndex:i];
//Create Annotation
Annotation *building = [[Annotation alloc] init];
building.title = [dataItem objectForKey:#"Title"];
building.subtitle = [dataItem objectForKey:#"Subtitle"];
MKCoordinateRegion buildingcoordinates = { {0.0, 0.0}, {0.0, 0.0} };
buildingcoordinates.center.latitude = [[dataItem objectForKey:#"Latitude"] floatValue];
buildingcoordinates.center.longitude = [[dataItem objectForKey:#"Longitude"] floatValue];
building.coordinate = buildingcoordinates.center;
[self.mapView addAnnotation:building];
}
[super viewDidLoad];
}
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString *pinIdentifier = #"pinIndentifier";
MKPinAnnotationView *pinView = (MKPinAnnotationView *)
[self.mapView dequeueReusableAnnotationViewWithIdentifier:pinIdentifier];
if (pinView == nil)
{
// if an existing pin view was not available, create one
MKPinAnnotationView *customPinView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:pinIdentifier];
customPinView.pinColor = MKPinAnnotationColorPurple;
customPinView.animatesDrop = YES;
customPinView.canShowCallout = YES;
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self
action:#selector(showDetails:)
forControlEvents:UIControlEventTouchUpInside];
customPinView.rightCalloutAccessoryView = rightButton;
return customPinView;
}
else
{
pinView.annotation = annotation;
}
return pinView;
}
Annotation.h:
#import <Foundation/Foundation.h>
#import <MapKit/MKAnnotation.h>
#interface Annotation : NSObject <MKAnnotation> {
CLLocationCoordinate2D coordinate;
NSString *title;
NSString *subtitle;
}
#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
Most likely the map view's delegate is not set which means the viewForAnnotation delegate method will not get called.
Since you've declared mapView as an IBOutlet, in the xib, make sure that the map view's delegate is connected to File's Owner.
Alternatively, at the top of the viewDidLoad method in MapViewController, set it programmatically:
mapView.delegate = self;
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];