I'm developing an iOS app with latest SDK.
I have a selector on the following method and I need to pass another argument:
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
NSLog(#"viewForAnnotation");
MKAnnotationView *annotationView = nil;
// if it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
if ([annotation isKindOfClass:[ShopAnnotation class]])
{
// try to dequeue an existing pin view first
static NSString *ReusableAnnotationIdentifier = #"reusableShopAnnotationIdentifier";
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[theMapView dequeueReusableAnnotationViewWithIdentifier:ReusableAnnotationIdentifier];
if (!pinView)
{
// if an existing pin view was not available, create one
MKPinAnnotationView *customPinView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:ReusableAnnotationIdentifier];
customPinView.pinColor = MKPinAnnotationColorPurple;
customPinView.animatesDrop = YES;
customPinView.canShowCallout = YES;
// add a detail disclosure button to the callout which will open a new view controller page
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self
action:#selector(showDetails:)
forControlEvents:UIControlEventTouchUpInside];
customPinView.rightCalloutAccessoryView = rightButton;
return customPinView;
}
else
{
pinView.annotation = annotation;
}
annotationView = pinView;
}
return annotationView;
}
I need to pass an object to showDetails::
// add a detail disclosure button to the callout which will open a new view controller page
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self
action:#selector(showDetails:)
forControlEvents:UIControlEventTouchUpInside];
This is how showDetails is implemented:
- (void) showDetails:(id)sender
{
NSLog(#"Show Details");
DetailViewController* mvc = [[DetailViewController alloc] initWithNibName:#"DetailViewController_iPhone" bundle:nil];
mvc.delegate = self;
[self presentModalViewController:mvc animated:YES];
}
This is ShopAnnotation interface:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#import <CoreData/CoreData.h>
#interface ShopAnnotation : NSObject <MKAnnotation>
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#property (nonatomic, readonly, strong) NSString *title;
#property (nonatomic, readonly, strong) NSString *subtitle;
#property (nonatomic, readonly, strong) NSManagedObject* shop;
-(id)initWithCoordinate:(CLLocationCoordinate2D) c
title:(NSString *) t
subtitle:(NSString *) st
shop:(NSManagedObject*) shop;
#end
How can I add another argument to showDetails and how can I pass it?
showDetails will be:
- (void) showDetails:(id)sender shop:(NSManagedObject*)shop
And, what is this (id)sender? It is an annotation I could use to pass this NSManagedObject.
See how you're passing a "sender" parameter in your "showDetails:" method?
Why not subclass "UIButton" (e.g. name it "VansFannelButton") and give that new object's "#interface" a bonus ivar which can be your payload.
Then you can do:
- (void) showDetails: (VansFannelButton*) sender
{
if (sender)
{
// do something with the managed object payload
NSManagedObject * mObject = [sender payload];
}
}
Use Associated Objects for passing more argument to buttons's object
Refer associated-objects link.
You can try adding a property
int ID;
into your ShopAnnotation class, and when you are populating the ShopAnnotation class you can easily set your own unique ID. Then in
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if (!pinView)
{
// if an existing pin view was not available, create one
MKPinAnnotationView *customPinView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:ReusableAnnotationIdentifier];
customPinView.pinColor = MKPinAnnotationColorPurple;
customPinView.animatesDrop = YES;
customPinView.canShowCallout = YES;
// add a detail disclosure button to the callout which will open a new view controller page
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self
action:#selector(showDetails:)
forControlEvents:UIControlEventTouchUpInside];
//Set tag of the button same as ID
rightButton.tag=((ShopAnnotation*)annotation).ID;
customPinView.rightCalloutAccessoryView = rightButton;
return customPinView;
}
}
And in you showDetails action you can do the following
- (void) showDetails:(id)sender
{
UIButton *btn=(UIButton*)sender;
ShopAnnotation *selectedAnnotation=nil;
for(ShopAnnotation *a in arrAnnotations){
if(a.ID == btn.tag){
selectedAnnotation=a; break;
}
}
// Use your ShopAnnotation for you further processing.
}
I hope this helps..
Thanks.
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 have create BubbleView in mapview. In bubble i have put the button but i have not click on button.
How can i implement please help me.
I have tried.......
customCalloutView.h
#import <UIKit/UIKit.h>
#interface customCalloutView : UIView
#property (nonatomic, retain) IBOutlet UIButton *BubbleBtn;
customCalloutView.m
#import "customCalloutView.h"
#implementation customCalloutView
{
}
#end
Delegate method of mapview in anotherclass
- (MKAnnotationView*)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
static NSString *identifier=#"MyAnnotation";
NSLog(#"anotioni s:%#",annotation);
//CLLocationCoordinate2D pinCoordinate = [self.pickupLocationView convertPoint:CGPointMake(self.pickupLocationView.frame.size.width / 2, self.pickupLocationView.frame.size.height) toCoordinateFromView:self.view];
MKAnnotationView *annotationView=(MKAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (!annotationView)
{
annotationView=[[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:identifier];
annotationView.image=[UIImage imageNamed:#"mapPin"];
} else
{
annotationView.annotation=annotation;
}
return annotationView;
}
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
customCalloutView *callOutView=nil;
callOutView.userInteractionEnabled=YES;
if (callOutView == nil)
{
NSArray *nib=[[NSBundle mainBundle]loadNibNamed:#"customCalloutView" owner:self options:nil];
callOutView=[nib objectAtIndex:0];
}
[callOutView.BubbleBtn addTarget:self action:#selector(BtnDetailclick:) forControlEvents:UIControlEventTouchUpInside];
CGRect callOutViewFrame=callOutView.frame;
callOutViewFrame.origin=CGPointMake(-callOutViewFrame.size.width/2+14 , -callOutViewFrame.size.height);
callOutView.frame=callOutViewFrame;
// [callOutView.BtnDetail setTitle:#"Change" forState:UIControlStateNormal];
[view addSubview:callOutView];
}
-(IBAction)BtnDetailclick:(UIButton *)sender
{
NSLog(#"Hello");
}
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...
I need to put custom annotation views to mapview, and they need to be clickable. i need rather than classic callout, i will tap on the annotation and it will perform some functions.
I tried many things but still not achieved.
I found a really good implementation for you:
http://blog.asolutions.com/2010/09/building-custom-map-annotation-callouts-part-1/
there you can add buttons to calloutView with actions
or..
in mapView delegate you can customize the annotation, and the callout view:
viewForAnnotation:
Returns the annotation view associated with the specified annotation object, if any.
(MKAnnotationView *)viewForAnnotation:(id < MKAnnotation >)annotation
there is an example:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"loc"];
// Button
UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
button.frame = CGRectMake(0, 0, 23, 23);
annotationView.rightCalloutAccessoryView = button;
// Image and two labels
UIView *leftCAV = [[UIView alloc] initWithFrame:CGRectMake(0,0,23,23)];
[leftCAV addSubview : yourImageView];
[leftCAV addSubview : yourFirstLabel];
[leftCAV addSubview : yourSecondLabel];
annotationView.leftCalloutAccessoryView = leftCAV;
annotationView.canShowCallout = YES;
return annotationView;
}
EDITED ANSWER FOR CUSTOM PIN:
make a MKAnnotationView subclass
.h
#import <MapKit/MapKit.h>
#interface AnnotationView : MKAnnotationView
- (id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier;
#end
.m
#import "AnnotationView.h"
#implementation AnnotationView
{
NSString *identifier;
}
- (id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self != nil)
{
CGRect frame = self.frame;
frame.size = CGSizeMake(78.0, 35.0);
self.frame = frame;
self.backgroundColor = [UIColor clearColor];
self.centerOffset = CGPointMake(0, 0);
}
return self;
}
-(void) drawRect:(CGRect)rect
{
[[UIImage imageNamed:#"yourImage"]drawInRect:CGRectMake(0, 0, 78, 35)];
}
#end
then in the mapview delegate:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]]) {
return nil;
}
if ([annotation isKindOfClass:[Annotation class]]) {
NSString* annotationIdentifier = #"yourAnnotation";
AnnotationView* customAnnotationView = (AnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
if (!customAnnotationView) {
customAnnotationView = [[AnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:annotationIdentifier];
customAnnotationView.canShowCallout = YES;
}
else{
customAnnotationView.annotation= annotation;
}
return customAnnotationView;
}
return nil;
}
Annotation class is my custom annotation class:
#interface Annotation : NSObject <MKAnnotation>
I hope this helps
There is a method on the mapViewDelegate: viewForMapView. In this method, you return a view. This view can be completely customized by you, e.g. you can add gestureregognizers to it to handle additional gestures beside tapping.
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];