iOS Mapkit Custom Callout - ios

Here's a sample custom callout that I'd want to get a similar style of. I was thinking of inheriting from MKAnnotation but I'm lost how to start it and I don't know if the callout's design could be overridden.
Any ideas how to implement this custom callout with ui controls inside?
EDIT: Here's my code from following the similar StackOverflow answer:
- (MKAnnotationView *)mapView:(MKMapView *)mapview viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString* AnnotationIdentifier = #"AnnotationIdentifier";
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if(annotationView)
return annotationView;
else
{
UIImage *img = [UIImage imageNamed:#"default_thumb.png"];
MKAnnotationView *annotationView =
[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
annotationView.canShowCallout = YES;
annotationView.image = img;
annotationView.draggable = YES;
/*
// change left accessory view button with image
UIImageView *leftAccView = [[UIImageView alloc] initWithImage:img];
annotationView.leftCalloutAccessoryView = leftAccView;
//include a right button to show more info
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self action:#selector(calloutSubMenu:) forControlEvents:UIControlEventTouchUpInside];
[rightButton setTitle:annotation.title forState:UIControlStateNormal];
annotationView.rightCalloutAccessoryView = rightButton;
*/
return annotationView;
}
return nil;
}
// Customize accessory view
- (void)mapView:(MKMapView *)mapview annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
[mapview deselectAnnotation:view.annotation animated:YES];
PopOverCallout *popOverCallout = [[PopOverCallout alloc]init];
UIPopoverController *popOver = [[UIPopoverController alloc] initWithContentViewController:popOverCallout];
self.annotationPopoverController = popOver;
popOver.popoverContentSize = CGSizeMake(500, 500);
[popOver presentPopoverFromRect:view.bounds inView:view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
The
(void)mapView:(MKMapView *)mapview annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
should be called whenever the pin is touched to show the callout right? but what happens is that a blank regular callout is shown. What might I be doing wrong?
BTW: the UIViewController subclass only has a label on it's XIB and is pretty much blank.

Found out the answer after a few mins of nap.
using this as the guide, I just needed to override
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
which shows the annotation callout. Thanks again for the answer, Ms. Anna.

Related

How to change UILabel value in CustomAnnotation by clicking button in iOS

I have added a custom annotation and a percentage label on it.
By pressing the button in red circle, I want to change value of label from percentage to business name.
My Code:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
static NSString *identifier = #"MyLocation";
if ([annotation isKindOfClass:[BusinessCustomAnnotation class]]) {
MKAnnotationView *annotationView = (MKAnnotationView *) [mapViewOffers dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
UILabel* category = [[UILabel alloc] initWithFrame:CGRectMake(annotationView.frame.size.width / 2, 15, 55, 20)];
BusinessCustomAnnotation *myAnnotationView = (BusinessCustomAnnotation *)annotation;
NSLog(#"Type One Offer! = %i", mapTypes);
[category setAdjustsFontSizeToFitWidth:YES];
if (mapTypes == 1) {
category.text = [NSString stringWithFormat:#"%#%#", myAnnotationView.offerPercentage, #"%"];
}else if (mapTypes == 2){
category.text = myAnnotationView.businessName;
}else if (mapTypes == 3){
category.text = myAnnotationView.businessName;
}
[category setMinimumScaleFactor:1.0];
category.font = [UIFont systemFontOfSize:15.0 weight:5.0];
category.textAlignment = NSTextAlignmentCenter;
[annotationView addSubview:category];
annotationView.enabled = YES;
annotationView.canShowCallout = NO;
annotationView.image = [UIImage imageNamed:#"iconMapMarker"];//here we use a nice image instead of the default pins
} else {
annotationView.annotation = annotation;
}
return annotationView; }return nil; }
Above mapview delegate is calling for one time only.
Waiting for the solution.
Thanks in advance for helping me.
There are two ways of detecting user interaction with your annotation view. The common technique is to define a callout (that standard little popover bubble that you see when you tap on a pin in a typical maps app) for your MKAnnotationView. And you create the annotation view for your annotation in the standard viewForAnnotation method:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"loc"];
annotationView.canShowCallout = YES;
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
return annotationView;
}
By doing this, you get a callout, but you're adding an right accessory, which is, in my example above, a disclosure indicator. That way, they tap on your annotation view (in my example above, a pin on the map), they see the callout, and when they tap on that callout's right accessory (the little disclosure indicator in this example), your calloutAccessoryControlTapped is called.
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
//first check your view class here
// here your code for change text on view
}
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
//first check your view class here
// here your code for change text on view
}
You need to refresh the annotations. In Action of button try this :
mapTypes = 2
for (id<MKAnnotation> annotation in mapView.annotations)
{
[mapView removeAnnotation:annotation];
[mapView addAnnotation:annotation];
}
You can't make any changes to already added annotation pin.To make any changes to the annotation pin you need to remove all the pin and add it back.
Annotations don't refresh.
You have to remove all existing annotations with
[self.mapView removeAnnotations:self.mapView.annotations];
and update your "mapTypes" variable value to "2" or "3" in order to show business name.
Then you can can add your annotations again with [MKMapView addAnnotation:].

Custom Annotation Callout View or Custom View ?

I'm currently building an "Uber-like" application, not in the same business but same design, and I would like to know what should I do if I want to represent their "Set pickup location" View/Button.
I already saw this post : Customize MKAnnotation Callout View? which was about Custom Annotation Callout View
In older version it looked like a Custom Callout View, but now it's a bit different, and I don't know where to start if I want to have the same result :
Thanks for your help !
To set different image on left accessory and right accessory, use this code.
// set different pins colors
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
MKAnnotationView *annotationView = nil;
if( [annotation isKindOfClass:[YOURANNOTATION class] ] )
{
static NSString * AnnotationID = #"YOURANNOTATION";
annotationView = [self.mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationID];
if( annotationView == nil )
{
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:AnnotationID] ;
}
UIImage * flagImage = nil;
flagImage = [UIImage imageNamed:#"marker-map#1x.png"];
[annotationView setImage:flagImage];
annotationView.canShowCallout = YES;
// add an image to the callout window
UIImageView *leftIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"marker-map#1x.png"]];
annotationView.leftCalloutAccessoryView = leftIconView;
//adding right button accessory
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView = infoButton;
//image size and adding image on left accessory
CGRect resizeRect;
resizeRect.size = flagImage.size;
resizeRect.origin = (CGPoint){0.0f, 0.0f};
UIGraphicsBeginImageContext(resizeRect.size);
[flagImage drawInRect:resizeRect];
UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
annotationView.image = resizedImage;
}
return annotationView;
}
Then to call selected annotation
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
YOURANNOTATION *annotation=(YOURANNOTATION*)view.annotation;
//do something with your annotation
}

MKAnnotationView custom button image

I am trying to use a custom image on my MKAnnotationView when I use the following code I get no image on my annotation. I have checked in debug to ensure the image is being properly loaded into the UIImage.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"String"];
if(!annotationView) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"String"];
UIButton *directionButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *directionIcon = [UIImage imageNamed:#"IconDirections"];
[directionButton setImage:directionIcon forState:UIControlStateNormal];
annotationView.rightCalloutAccessoryView = directionButton;
}
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
return annotationView;
}
There are two main issues:
The frame of the custom callout button is not set making it essentially invisible.
An MKAnnotationView is being created but its image property (the image of the annotation itself -- not the callout button's) is not set. This makes the whole annotation invisible.
For issue 1, set the button's frame to some appropriate value. For example:
UIImage *directionIcon = [UIImage imageNamed:#"IconDirections"];
directionButton.frame =
CGRectMake(0, 0, directionIcon.size.width, directionIcon.size.height);
For issue 2, set the annotation view's image (or create an MKPinAnnotationView instead):
annotationView.image = [UIImage imageNamed:#"SomeIcon"];
Additionally, you should handle view re-use correctly by updating the annotation property.
Complete example:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"String"];
if(!annotationView) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"String"];
annotationView.image = [UIImage imageNamed:#"SomeIcon"];
UIButton *directionButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *directionIcon = [UIImage imageNamed:#"IconDirections"];
directionButton.frame =
CGRectMake(0, 0, directionIcon.size.width, directionIcon.size.height);
[directionButton setImage:directionIcon forState:UIControlStateNormal];
annotationView.rightCalloutAccessoryView = directionButton;
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
}
else {
//update annotation to current if re-using a view
annotationView.annotation = annotation;
}
return annotationView;
}
In order for the callout to be shown, the annotation must be selected. To do this programmatically, call:
[mapView selectAnnotation:annotation animated:YES];
where annotation is the specific MKAnnotation for which you want a callout displayed.
You'll almost certainly want to put this in - (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views.
There are a few caveats to consider, so here are two other posts that have some great answers and relevant discussions:
How to trigger MKAnnotationView's callout view without touching the pin?
Wanted: How to reliably, consistently select an MKMapView annotation

Dragging the map after dragging my MKAnnotationView does not move the MKAnnotationView with it

MKPinAnnotationView does not allow you to use a custom image as 'pin' and enable dragging at the same time, because the image will change back to the default pin as soon as you start dragging. Therefore I use an MKAnnotationView instead of an MKPinAnnotationView.
While using MKAnnotationView instead of MKPinAnnotationView does keep your custom image shown as your 'pin' it doesn't support the drag & drop animation that you get with the default pin.
Anyway, my issue is that after I drag my custom MKAnnotationView to a new point on the map and then move the map itself the MKAnnotationView does not move with the map anymore.
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
static NSString *defaultID = #"myLocation";
if([self.annotation isKindOfClass:[PinAnnotation class]])
{
//Try to get an unused annotation, similar to uitableviewcells
MKAnnotationView *annotationView = [self.mapView dequeueReusableAnnotationViewWithIdentifier:defaultID];
//If one isn't available, create a new one
if(!annotationView)
{
annotationView = [[MKAnnotationView alloc] initWithAnnotation:self.annotation reuseIdentifier:defaultID];
annotationView.canShowCallout = YES;
annotationView.draggable = YES;
annotationView.enabled = YES;
}
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 32, 32)];
imgView.image = self.passableTag.image;
annotationView.leftCalloutAccessoryView = imgView;
annotationView.image = [UIImage imageNamed:[Constants tagIconImageNameForTagType:self.passableTag.type]];
return annotationView;
}
return nil;
}
Just set the annotationView.dragState to MKAnnotationViewDragStateNone after ending the drag:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState {
if ([view.annotation isKindOfClass:[PinAnnotation class]]) {
if (newState == MKAnnotationViewDragStateEnding) {
view.dragState = MKAnnotationViewDragStateNone;
}
}
}
I had the same problem and I solved it adding "setDragState" to my MKAnnotationView class.
This is an old solution but it worked to me (iOS8).
Don't reuse MKAnnotationView and it will work.

Error when adding an action to a pin in map view

I am trying to make an application where various pins are placed on the map.
When I touch a pin, the standard bubble pops up with a disclosure button. When I click my disclosure button, I would like it to open up a new ViewController so I can display more information.
The problem is that every time I run the application, it crashes and it gives me an error in the output.
How could I fix this?
The error in the output is:
2013-06-29 16:51:33.575 ...[2723:13d03] -[FirstViewController
...Clicked:]: unrecognized selector sent to instance 0x867d0d0
2013-06-29 16:51:33.576 lam[2723:13d03] * Terminating app due to
uncaught exception 'NSInvalidArgumentException', reason:
'-[FirstViewController ...Clicked:]: unrecognized selector sent
to instance 0x867d0d0'
* First throw call stack: (0x1d8d012 0x11cae7e 0x1e184bd 0x1d7cbbc 0x1d7c94e 0x11de705 0x182c0 0x18258 0xd9021 0xd957f 0xd86e8 0x47cef
0x47f02 0x25d4a 0x17698 0x1ce8df9 0x1ce8ad0 0x1d02bf5 0x1d02962
0x1d33bb6 0x1d32f44 0x1d32e1b 0x1ce77e3 0x1ce7668 0x14ffc 0x1e12
0x1d45) libc++abi.dylib: terminate called throwing an exception
The code in my FirstViewController.m is:
[...]
[...]
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
MKPinAnnotationView *MyPin=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"current"];
MyPin.pinColor = MKPinAnnotationColorPurple;
UIButton *advertButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
if ([[annotation title] isEqualToString:#"..."]) {
[advertButton addTarget:self action:#selector(...Clicked:) forControlEvents:UIControlEventTouchUpInside];
}
if ([[annotation title] isEqualToString:#"..."]) {
[advertButton addTarget:self action:#selector(...Clicked:) forControlEvents:UIControlEventTouchUpInside];
}
MyPin.rightCalloutAccessoryView = advertButton;
MyPin.draggable = NO;
MyPin.highlighted = YES;
MyPin.animatesDrop=TRUE;
MyPin.canShowCallout = YES;
return MyPin;
}
- (void)mapView:(MKMapView *)mapView MyPin:(MKPinAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
// Go to edit view
UIViewController *detailViewController = [[UIViewController alloc] initWithNibName:#"...Controller" bundle:nil];
[self.navigationController pushViewController:detailViewController animated:YES];
}
//-(void) ...Clicked:(id)sender {
// NSLog(#"... Clicked");
//}
-(void) ...:(id)sender {
NSLog(#"...");
}
-(IBAction)findmylocation:(id)sender {
mapView.showsUserLocation = YES;
mapView.delegate = self;
[mapView setUserTrackingMode:MKUserTrackingModeFollow animated:YES];
}
What causes the error is:
- (void)mapView:(MKMapView *)mapView MyPin:(MKPinAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
// Go to edit view
UIViewController *detailViewController = [[UIViewController alloc] initWithNibName:#"..." bundle:nil];
[self.navigationController pushViewController:detailViewController animated:YES];
}
EDIT 2:
I have removed the advertButton addTarget lines, but now I don't have the disclosure button when I run the simulator... is what I removed correct?
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
MKPinAnnotationView *MyPin=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"current"];
MyPin.pinColor = MKPinAnnotationColorPurple;
UIButton *advertButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
if ([[annotation title] isEqualToString:#"..."])
if ([[annotation title] isEqualToString:#"..."])
The error:
[FirstViewController ...Clicked:]: unrecognized selector sent to instance
means that it's trying to call the ...Clicked: method on FirstViewController (because you set the advertButton's target to that method) but there is no such method (you've commented it out).
You could just un-comment it.
But since you are using the calloutAccessoryControlTapped delegate method anyway to detect accessory taps (a better choice than a custom method), you should instead do the following:
Remove the advertButton addTarget lines from viewForAnnotation and just handle the taps in calloutAccessoryControlTapped.
Also remove the ...Clicked: method.
Another issue is the method calloutAccessoryControlTapped is incorrectly named as mapView:MyPin:calloutAccessoryControlTapped:.
The method must be named mapView:annotationView:calloutAccessoryControlTapped: like this:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
// Go to edit view
UIViewController *detailViewController = [[UIViewController alloc] initWithNibName:#"...Controller" bundle:nil];
[self.navigationController pushViewController:detailViewController animated:YES];
}
By the way, in the calloutAccessoryControlTapped, you'll be able to access the annotation that was tapped through view.annotation.
The updated viewForAnnotation should be something like this:
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
MKPinAnnotationView *MyPin=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"current"];
MyPin.pinColor = MKPinAnnotationColorPurple;
UIButton *advertButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
MyPin.rightCalloutAccessoryView = advertButton;
MyPin.draggable = NO;
MyPin.highlighted = YES;
MyPin.animatesDrop=TRUE;
MyPin.canShowCallout = YES;
return MyPin;
}

Resources