How to add Button to userLocation callout? - iphone

How can I display a DisclosureButton in the userLocation (the blue dot) callout like the Apple Maps app does?
Now I just can change its title and subtitle, but I hope to provide custom callout views. Therefore, if I click the blue dot, it can display a button besides only title and subtitle. In this way, when I click the button, it can guide me to another View.
I returned MKAnnotationView in viewForAnnotation when annotation == self.mapView.userLocation. However, the callout can display updated title and subtitle, but cannot show the button I added.
I really need this function, and I desire any help and suggestion!

I've tried camdaochemgio's solution, but it works only for the second tap. Here is a solution which will always work:
-(void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
for (MKAnnotationView* view in views)
{
if ([view.annotation isKindOfClass:[MKUserLocation class]])
{
view.rightCalloutAccessoryView=[UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
}
}

For the annotationView, set
myAnnotationView.canShowCallout=YES;
then add up to two buttons with
myAnnotationView.leftCalloutAccessoryView=myLeftButton;
myAnnotationView.rightCalloutAccessoryView=myRightButton;
The buttons can be a normal button, a custom button, or one of the predefined types. Like this:
myAnnotationView.rightCalloutAccessoryView=[UIButton buttonWithType:UIButtonTypeDetailDisclosure];
Then add the calloutAccessoryControlTapped: method to handle taps.

maybe late.
Here is solution. You implement the UIMapViewDelegete method as below:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
if ([view.annotation isKindOfClass:[MKUserLocation class]] && !view.rightCalloutAccessoryView) {
view.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
}

Related

mapView:viewForAnnotation: not called when delegate set in code

I have a custom class for my MKAnnotations, I want to override the default mapView:viewForAnnotatation method so that I can add extra information in the callout. When I set my delegate in code (as per the code below) the annotations are dropped on the map and are selectable but my mapView:viewForAnnoation is never called.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
NSLog(#"viewForAnnotation: called");
MKAnnotationView *aView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"mapPin"];
if(!aView){
aView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"mapPin"];
}
aView.annotation = annotation;
return aView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.mapView.delegate = self;
}
I know the delegate is being set as I can override the method -(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view and I see an NSLog when I select an annotation.
When I change from setting the delegate in code to setting it in the Storyboard the method is called (NSLog(#"viewForAnnotation: called"); statements appear) but the annotations don't appear on the map and sometimes this error appears:
<Error>: ImageIO: CGImageReadSessionGetCachedImageBlockData *** CGImageReadSessionGetCachedImageBlockData: bad readSession [0x8618480]
This seems like two separate issues:
Regarding the setting delegate in code v storyboard, it's hard to reconcile your various observations (delegate method didSelectAnnotationView is getting called in both scenarios, but viewForAnnotation is not). The only difference between setting it in code v in the storyboard is the timing of when the delegate is getting set. You're not showing us the process of adding the annotations, so it's hard to diagnose on the basis of what you've described. If none of your delegate methods were getting called, I'd suspect the mapView IBOutlet, but if some are working and others aren't, I can only suspect a timing issue.
Regarding the setting of the MKAnnotationView, the default implementation does nothing. You either need to write your own subclass of MKAnnotationView, set its image if you're using your own image, or, much easier, just use MKPinAnnotationView. But just creating a MKAnnotationView won't do anything. You really want:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
// If it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
// Handle any custom annotations.
MKAnnotationView *aView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"mapPin"];
if(aView){
aView.annotation = annotation;
} else {
aView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"mapPin"];
}
aView.canShowCallout = NO;
return aView;
}
(Note, I'm not only creating a MKPinAnnotationView, but I'm also making sure that it's not a MKUserLocation, in case you ever choose to show user location on the map. I'm also going to explicitly set the canShowCallout, as that's presumably the reason you're writing this method at all.)
Bottom line, if you want to show a simple pin annotation view, use MKPinAnnotationView. Using MKAnnotationView alone will result in no annotations from appearing.
In case someone else is searching for a reason why mapView:viewForAnnotatation is not being called when the delegate is set in code, there is a bug in iOS 6 - http://openradar.appspot.com/12346693
I came across the same issue and I wanted to share my solution:
I also override
(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation
But I realised that these Annotations work similar to TableView and iOS will reuse the annotations like the cells in TVC (table view controller)
Since I was only using one Identifier mapView dequeueReusableAnnotationViewWithIdentifier:#"mapPin" it would not need to call ViewForAnnotation again if it had enough 'annotation' in memory.
So my solution was to create multiple Identifier the first time the map load based on my conditions.
This solved my problem.
i just dropped to this question accidentally when i was searching for the same problem.
I solved my problem, in my case im resizing the mapview.
I added delegate after i resize the mapview. it works now perfectly.!

How do I know which callout has been clicked?

I'm having alot of trouble trying to identify which callout bubble has been clicked.
Is there a way of identifying the callout bubbles some how as I've tried a number of examples on here but because I'm quite new to Objective C I can't seem to work out how to use them.
The following answers I've been trying to use but I'm confused with them
How to track which annotation callout clicked
I'm looking at tagging the annotations but thats even confusing me. :(
Taken from the second answer in the above link:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]]){
return nil;
if(annotation.tag == 111)
//Do something
else
//Do some other thing
}
How does the first part of this snippet work, perhaps if I understood it better I'd know what I'm working with.
Also how would I go about retrieving the tags when the callout is clicked so I can make the app respond respectively.
You can subclass MKPinAnnotationView so you can identify it when
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKPinAnnotationView *)view
is called.
You have to create class which extends MKAnnotation and create some property like tag and assign unique value or tag and add in mapview like.
Place* home = [[Place alloc] init] ;
home.name = [dForMap valueForKey:#"shortaddress"];
home.latitude = [[dForMap valueForKey:#"latitude"]floatValue];
home.longitude = [[dForMap valueForKey:#"longitude"]floatValue];
home.description = [dForMap valueForKey:#"shortaddress"];
home.flierid = count;
PlaceMark* from = [[PlaceMark alloc] initWithPlace:home] ;
from.flierid = count;
from.mapAnnotationType = MapAnnotationTypeProperty;
[self addAnnotation:from];
you can check the property in didSelectAnnotationView method like this way when callout bubble is clicked.
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
PlaceMark *a = (PlaceMark*)view.annotation;
Place *test = a.place;
int tag = test.flierid;
}
Thanks.

IPhone - Removing the 'currentLocation' blue dot from an MKMapView

I'm trying to replace the blue 'currentLocation' dot with a custom annotation. That code is working well (I am just implementing viewForAnnotation and cycling through that, replacing the annotation ofClass MKUserLocation with a custom image).
However, once I replace the annotation, the user's current location stops updating. All related functions (like didUpdateUserLocation) thus stop being called. This causes a lot of problems. I tried implementing the various code blocks at MKUserLocation Custom View not moving! but I couldn't get it to work. After extensive Googling and stackoverflow searching, I've come up with nothing.
Does anyone have a solution to this problem?
Here is the code for the viewForAnnotation method:
_userDot is an instance variable within MapScreen.m (the place where all of this code is). It's alloced in the viewDidLoad and is of type MKAnnotationView. Basically I couldn't just get rid of the annotation, so I wanted to set it to an invisible image instead (currently 1x1 for debugging).
- (MKAnnotationView *)mapView:(MKMapView *)newMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]]) {
//Initialize a new MKAnnotationView using the current userLocation annotation
//Then make it invisible (because we are cheap like that)
//TODO: Make it actually invisible (right now it is 1x1 for debugging)
if (_userDot)
{
_userDot.annotation = annotation;
_userDot.image = [UIImage imageNamed:#"mister_taco"];
_userDot.frame = CGRectMake(1, 1, 1, 1);
return _userDot;
}
else
return nil;
}
else {*/
return nil;
//}
}
Thanks,
Brian

iPhone MKAnnotation with an ID for database content

Im trying to use the MKMap API and integrate a database table id so I can click a button detail disclosure to send the user to another page with further information. Ive been all over teh MKMapKit on the Apple site to find some property or method to help me with this and went over a few tutorials with no answers.
Ive tried to attach the id into the subtitle context so I can retrieve it in the MKAnnotationView where I make my MKPinAnnotationView and add teh button to the rightCalloutAccessoryView. It errors out and doesnt want to work.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
MKPinAnnotationView *annView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"mkpin"];
annView.animatesDrop = YES;
annView.canShowCallout = YES;
UIButton *disclosureButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annView.rightCalloutAccessoryView = disclosureButton;
return annView;
}
I used this technique for an individual pin on a map and it seemed to work but I didnt require an id...
When I try to get the subtitle of the annotation it kills the app. I know the (id)annotation is an integer but I dont know how to manipulate this information. This function above I think is called after my code:
[mapView addAnnotations:markers]; //where markers is an array of title, subtitle aka id
Any help would be greatly appreciated.
The subtitle property is for a subtitle, not a database id. No good can come from abusing it in that way. Even if you could get it to work, it's a horrible approach.
All you have to do is give your annotation class an appropriate property to store your id. When you are passed the annotation, you can access it from there by casting to your annotation class. When you are passed the annotation view in other delegate methods, you can access the annotation through its annotation property.

Hiding map annotations without deleting them

Using a MKMapView I have a pile of annoatations loaded onto it and I want to be able to filter the annotations displayed with a segmented control.
I'm using custom annotations with a type variable so I can tell them apart from one another but I haven't been able to find a way to hide and display a subset of annotation views at will.
Sure, try this:
Objective-C solution:
[[yourMapView viewForAnnotation:yourAnnotation] setHidden:YES]
Swift 4 solution:
yourMapView.view(for: yourAnnotation)?.isHidden = true
This will return you the view associated with the specified annotation object, then you can set the view to hidden. Here is the documentation.
if you want to hide the MKAnnotationView (bubble) you can create a custom one:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if (annotation==self.map.mapView.userLocation)
return nil;
MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"loc"];
if([annotation isKindOfClass:[AnnotationCustomClass class]] ) {
annotationView.canShowCallout = NO; // <- hide the bubble
}
return annotationView;
}