Hey all, I got somewhat of a dense question about the mapKit for the iPhone.
I'm using the MapKit framework and what I'm trying to do is basically click a pin, reload it and then show it's callOut after it has been added again.
This is the code I'm trying to get to work..
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
NSLog(#"count of selected Annotations: %d",[mapView selectedAnnotations].count);
MKAnnotation* pin = view.annotation;
[mapView deselectAnnotation:pin animated:FALSE];
[mapView removeAnnotation:pin];
[mapView addAnnotation:pin];
[self.mapView selectAnnotation:pin animated:TRUE];
A few observations: If I comment the removeAnnotations and addAnnotation lines out, I enter an infinite loop because when I selectAnnotation:pin, the callback (which is this method) is called... otherwise, it isn't, but then what is? why isn't
[self.mapView selectAnnotation:pin animated:TRUE];
being called?
I've already read far too much and broke my head for far too many hours trying to figure this out that an explanation and a fix to my code would be much more helpful than a link.
Thanks in advance.
~Fydo
So I've answered my Own Question... it seems that the easiest way to change the annotation upon clicking it, is as follows:
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
MKAnnotation* pin = view.annotation;
UIImageView * blackPin = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"PinUnchecked.png"]];
[[mapView viewForAnnotation:pin] addSubview:blackPin];
This delegate method will be called, then an annotationView bubble will be displayed AND the annotationView will change it's image... which is all I needed done...
Related
please help, this question has been asked so many times before, but peoples suggestions have no effect on my outcome, all i want, is the pin and blue circle (Accuracy) to be shown on the map, here is my implementation. - oh, im using an iPhone device - im not in the simulator
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
NSLog(#"View for Annotation is called");
if (NSClassFromString(#"MKUserLocation")==[annotation class]) {
return nil;
}
if (annotation == mapView.userLocation) {
return nil;
}
MKPinAnnotationView *annView=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"currentloc"];
annView.pinColor = MKPinAnnotationColorGreen;
UIButton * btn = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annView.rightCalloutAccessoryView = btn;
annView.animatesDrop=TRUE;
annView.canShowCallout = YES;
annView.calloutOffset = CGPointMake(-5, 5);
pinDropped = TRUE;
return annView;
}
cheers in advance.... bloody thing
If you're using the Simulator then you're likely encountering the not-well-known difference in the way that CoreLocation and MapKit figure your current position.
In the Simulator
CoreLocation will always use Apple's headquarters in Cupertino, California, USA as your current location. This is where the blue dot will always appear on the map.
MapKit will always use something approximating your actual current location using Apple's database of IP address and WiFi hotspot information. This is where the map will center if you tell it to use your current location.
As a result, the map will center on your current location but the blue dot will be over in Cupertino.
On an iOS Device
CoreLocation puts the blue dot on something approximating your actual current location.
MapKit centers the map on something approximating your actual current location.
As a result you'll see the blue dot in the center of the map when using an actual iOS device.
This bit of knowledge can save a lot of stress. :)
just in case someone was wondering, this also works:
if ([annotation isKindOfClass:[MKUserLocation class]])
{
return nil;
}
This works in my code
if(annotation == mapView.userLocation){
return nil;
}
Try that and let us know
make sure implemented [mapview addAnnotation:annotation];
thanks Anna for the reminder.
I'm new to programming and obj-c and currently working on a map based iphone app which will locate different sport facilities. I want each pin to show a different overlay, for instance a picture overlay will do fine, I'll add pics into the resource folder and I want each pin to reveal a different picture by clicking it. My example only includes one pin with its cordinates.
Now, I have now clue where to start, hope u can help me out!
thanks in advance :)
Here's my code:
[mapView addAnnotation:[MapLocation mapLocationtWithTitle:#"Beckomberga Sim och Sporthall" subtitle:#"Söderberga Allé 80" andCoordinate:(CLLocationCoordinate2D){ 59.35817, 17.89740 }]];
}
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if([annotation isKindOfClass:[MapLocation class]])
{
MKPinAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"PinAnnotation"];
pin.canShowCallout = YES;
pin.animatesDrop = NO;
pin.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
pin.pinColor = MKPinAnnotationColorRed;
pin.pinColor = MKPinAnnotationColorGreen;
[pin autorelease];
return pin;
}
return nil;
}
Viktor,
Here is what you need to do:
Create a subobject of MKAnnotation that will represent your locations, SportsFacilityLocation
That object will contain title, subtitle, and location fields. This is to conform to the MKAnnotation protocol
Next you need an object that will be a subclass of MKAnnotationView, SportsFacilityMapView.
Finally in your viewForAnnotation delegate method you need to check each annotation type
and from that annotation type you will determine to return your SportsFacilityMapView
Check the docs on how to implement the Annotation and then AnnotationView. I have some example code at home that I will post later if you need it. Good Luck!
So, I've created a CLLocationManager, called it to start updating, set mapView.showsUserLocation to YES, and returned nil for the userLocation annotation.
Here are some snippets from my code in my UIMapViewController:
- (void)viewDidLoad
{
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
[locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
CLLocationCoordinate2D userCoordinate = locationManager.location.coordinate;
[map setCenterCoordinate:userCoordinate animated:YES];
[map setShowsUserLocation:YES];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView *mapIconView = (MKAnnotationView *) [mapView dequeueReusableAnnotationViewWithIdentifier:#"mapIconView"];
// Don't mess with the user location annotation
if (annotation == mapView.userLocation)
return nil;
// etc.
}
This all seems pretty straightforward. Everything works fine--the map zooms to my location as it should, and I've confirmed that all the methods are called as expected--but no blue dot. Can't get the blue dot for the life of me, no matter where or how many times I say
mapView.setUserLocation = YES;
What am I missing here?
When I do this, rather than checking annotation like you have, I do something along the lines of:
if([annotation class] == MKUserLocation.class) {
return nil;
}
I had this same problem where the blue dot wouldn't appear. Turns out it was appearing, just not where I thought it was.
Similarly to my issue, your code looks like it's both processing location updates and asking the MKMapView to track the user location. Note that in the simulator, these are two different locations! When the MKMapView is tracking the user location in the simulator, it gives you the location of Apple in Cupertino, CA, regardless of your "actual" location.
Using this code and letting the MKMapView track the location for your delegate instead of tracking the location yourself can reveal the blue dot:
- (void)mapView:(MKMapView *)theMapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
[theMapView setCenterCoordinate:userLocation.location.coordinate animated:YES];
}
You'll probably want to zoom in a bit. I used the handy code from this blog entry to do that: Set the Zoom Level of an MKMapView
It's mapView.showUserLocation = YES;
You seem to have used the wrong statement 'set' instead of 'show'.
And FYI, you don't have to mess with the location manager just to get the userlocation. Corelocation automatically gets fired when you set mapview.showUserLoction = YES and load the map.
Hope it helps :)
Make sure that you are not removing all annotations from the map anywhere:
e.g. [mapView removeAnnotations:mapView.annotations]
If you are using the iOS simulator, then you can't see the blue dot. I had the exact same problem and when I started to try my software with the real hardware the blue dot showed up!
I'm using MapKit to display the user's location relative to pins around them. I'd like to be able to mimic the functionality that Maps provides via the crosshair button in the lower left-hand corner of the screen. I'm already aware that MapKit provides a CLLocation object with the user's location via MKUserLocation, I just wanted to seek advice on how I should keep focus on that location. My initial inclination was to use an NSTimer to center the map on that coordinate every 500ms or so.
Is there a better way to do this? Is there something built in to MapKit that I'm missing that will accomplish this?
Thanks so much,
Brendan
If you're on IOS5+ this is VERY easy. Just change the "userTrackingMode" using code such as:
[_mapView setUserTrackingMode:MKUserTrackingModeFollow animated:YES];
This will smoothly follow the users current location. If you drag the map it will even set the tracking mode back to MKUserTrackingModeNone which is usually the behaviour you want.
It's really simple to have the map update the user location automatically just like the google maps. Simply set showsUserLocation to YES
self.mapView.showsUserLocation = YES
...and then implement the MKMapViewDelegate to re-center the map when the location is updated.
-(void) mapView:(MKMapView *)mapView
didUpdateUserLocation:(MKUserLocation *)userLocation
{
if( isTracking )
{
pendingRegionChange = YES;
[self.mapView setCenterCoordinate: userLocation.location.coordinate
animated: YES];
}
}
And to allow the user to zoom & pan without stealing the view back to the current location...
-(void) mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
{
if( isTracking && ! pendingRegionChange )
{
isTracking = NO;
[trackingButton setImage: [UIImage imageNamed: #"Location.png"]
forState: UIControlStateNormal];
}
pendingRegionChange = NO;
}
-(IBAction) trackingPressed
{
pendingRegionChange = YES;
isTracking = YES;
[mapView setCenterCoordinate: mapView.userLocation.coordinate
animated: YES];
[trackingButton setImage: [UIImage imageNamed: #"Location-Tracking.png"]
forState: UIControlStateNormal];
}
I think that I would actually use the CoreLocation CLLocationManager and use its delegate method locationManager:didUpdateToLocation:fromLocation:.
This way, you don't have the overhead of an NSTimer, and it only updates when there's a new location available.
You can pull the longitude and latitude from the CLLocation object sent to the locationManager:didUpdateToLocation:fromLocation: method and pass it to the map view.
I go with Jacob Relkin's answer. This tutorial provides a step-by-step procedure of using CoreLocation in an iPhone app. Hope this helps you.
All the Best.
I'm working on a MKMapView with the usual colored pin as the location points. I would like to be able to have the callout displayed without touching the pin.
How should I do that? Calling setSelected:YES on the annotationview did nothing. I'm thinking of simulate a touch on the pin but I'm not sure how to go about it.
But there is a catch to get benvolioT's solution to work, the code
for (id<MKAnnotation> currentAnnotation in mapView.annotations) {
if ([currentAnnotation isEqual:annotationToSelect]) {
[mapView selectAnnotation:currentAnnotation animated:FALSE];
}
}
should be called from - (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView, and nowhere else.
The sequence in which the various methods like viewWillAppear, viewDidAppear of UIViewController and the - (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView is called is different between the first time the map is loaded with one particular location and the subsequent times the map is displayed with the same location. This is a bit tricky.
Ok, here's the solution to this problem.
To display the callout use MKMapView's selectAnnotation:animated method.
Assuming that you want the last annotation view to be selected, you can put the code below:
[mapView selectAnnotation:[[mapView annotations] lastObject] animated:YES];
in the delegate below:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
//Here
[mapView selectAnnotation:[[mapView annotations] lastObject] animated:YES];
}
Ok, to successfully add the Callout you need to call selectAnnotation:animated after all the annotation views have been added, using the delegate's didAddAnnotationViews:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views{
for (id<MKAnnotation> currentAnnotation in mapView.annotations) {
if ([currentAnnotation isEqual: annotationToSelect]) {
[mapView selectAnnotation:currentAnnotation animated:YES];
}
}
}
After trying a variety of answers to this thread, I finally came up with this. It works very reliably, I have yet to see it fail:
- (void)mapView:(MKMapView *)aMapView didAddAnnotationViews:(NSArray *)views;
{
for(id<MKAnnotation> currentAnnotation in aMapView.annotations)
{
if([currentAnnotation isEqual:annotationToSelect])
{
NSLog(#"Yay!");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_current_queue(), ^
{
[aMapView selectAnnotation:currentAnnotation animated:YES];
});
}
}
}
The block is used to delay slightly, as without it the callout may not be shown correctly.
This does not work for me. I suspect a bug in the MapKit API.
See this link for details of someone else for who this is not working:
http://www.iphonedevsdk.com/forum/iphone-sdk-development/19740-trigger-mkannotationview-callout-bubble.html#post110447
--edit--
Okay after screwing with this for a while, here is what I've been able to make work:
for (id<MKAnnotation> currentAnnotation in mapView.annotations) {
if ([currentAnnotation isEqual:annotationToSelect]) {
[mapView selectAnnotation:currentAnnotation animated:FALSE];
}
}
Note, this requires implementing - (BOOL)isEqual:(id)anObject for your class that implements the MKAnnotation protocol.
If you just want to open the callout for the last annotation you added, try this, works for me.
[mapView selectAnnotation:[[mapView annotations] lastObject] animated:YES];
The problem with calling selectAnnotation from - (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView is that, as the name implies, this event is only triggered once your MapView loads initially, so you won't be able to trigger the annotation's callout if you add it after the MapView has finished loading.
The problem with calling it from - (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views is that your annotation may not be on-screen when selectAnnotation is called which would cause it to have no effect. Even if you center your MapView's region to the annotation's coordinate before adding the annotation, the slight delay it takes to set the MapView's region is enough for selectAnnotation to be called before the annotation is visible on-screen, especially if you animate setRegion.
Some people have solved this issue by calling selectAnnotation after a delay as such:
-(void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
[self performSelector:#selector(selectLastAnnotation)
withObject:nil afterDelay:1];
}
-(void)selectLastAnnotation {
[myMapView selectAnnotation:
[[myMapView annotations] lastObject] animated:YES];
}
But even then you may get weird results since it may take more than one second for the annotation to appear on-screen depending on various factors like the distance between your previous MapView's region and the new one or your Internet connection speed.
I decided to make the call from - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated instead since it ensures the annotation is actually on-screen (assuming you set your MapView's region to your annotation's coordinate) because this event is triggered after setRegion (and its animation) has finished. However, regionDidChangeAnimated is triggered whenever your MapView's region changes, including when the user just pans around the map so you have to make sure you have a condition to properly identify when is the right time to trigger the annotation's callout.
Here's how I did it:
MKPointAnnotation *myAnnotationWithCallout;
- (void)someMethod {
MKPointAnnotation *myAnnotation = [[MKPointAnnotation alloc] init];
[myAnnotation setCoordinate: someCoordinate];
[myAnnotation setTitle: someTitle];
MKCoordinateRegion someRegion =
MKCoordinateRegionMakeWithDistance (someCoordinate, zoomLevel, zoomLevel);
myAnnotationWithCallout = myAnnotation;
[myMapView setRegion: someRegion animated: YES];
[myMapView addAnnotation: myAnnotation];
}
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
if (myAnnotationWithCallout)
{
[mapView selectAnnotation: myAnnotationWithCallout animated:YES];
myAnnotationWithCallout = nil;
}
}
That way your annotation is guaranteed to be on-screen at the moment selectAnnotation is called, and the if (myAnnotationWithCallout) part ensures no region setting other than the one in - (void)someMethod will trigger the callout.
I read the API carefully and finally I found the problem:
If the specified annotation is not onscreen, and therefore does not have an associated annotation view, this method has no effect.
So you can wait some time (for example, 3 seconds) and then perform this action. Then it works.
Due to something like the code shown by benvolioT, that I suspect exists in the system, when I used selectAnnotation:animation: method, it did not show the callOut, I guessed that the reason was because it was already selected and it was avoiding from asking the MapView to redraw the callOut on the map using the annotation title and subtitle.
So, the solution was simply to deselect it first and to re-select it.
E.g: First, I needed to do this in Apple's touchMoved method (i.e. how to drag an AnnotationView) to hide the callOut. (Simply using annotation.canShowAnnotation = NO alone does not work, since I suspect that it needs redrawing. The deselectAnnotaiton causes the necessary action. Also, deselecting alone did not do that trick, the callOut disappeared only once and got redrawn straight away. This was the hint that it got reselected automatically).
annotationView.canShowAnnotation = NO;
[mapView deselectAnnotation:annotation animated:YES];
Then, simply using the code below in touchEnded method did not bring back the callOut (The annotation has been automatically selected by the system by that time, and presumably the redrawing of the callOut never occrrs):
annotationView.canShowAnnotation = YES;
[mapView selectAnnotation:annotation animated:YES];
The solution was:
annotationView.canShowAnnotation = YES;
[mapView deselectAnnotation:annotation animated:YES];
[mapView selectAnnotation:annotation animated:YES];
This simply bought back the callOut, presumably it re-initiated the process of redrawing the callOut by the mapView.
Strictly speaking, I should detect whether the annotation is the current annotation or not (selected, which I know it is) and whether the callOut is actually showing or not (which I don't know) and decide to redraw it accordingly, that would be better. I, however, have not found the callOut detection method yet and trying to do so myself is just a little bit unnecessary at this stage.
Steve Shi's response made it clear to me that selectAnnotation has to be called from mapViewDidFinishLoadingMap method. Unfortunately i cannot vote up but i want to say thanks here.
Just add [mapView selectAnnotation:point animated:YES];
Resetting the annotations also will bring the callout to front.
[mapView removeAnnotation: currentMarker];
[mapView addAnnotation:currentMarker];