how to know viewForAnnotation is no more being called? - iphone

I'm wondering...how to know that that method is no more being called?
method:
mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
}
How to know the above method is no more called?
(From the comments:)
I have a method add location which calculates le coordinates from an array of coordinates and places them on the map, then the mapView:(MKMapView *) mapView viewForAnnotation:(id ) annotation { } is being called to place the different custom image instead of the pins...
this takes around 30 seconds....
meanwhile I have started to animate an activity indicator so as the user does not think that the app has crashed....
I want to stop the activity indicator when it has finished...

You can use the didAddAnnotationViews delegate method to tell when the annotation views have been displayed on the map and stop the activity indicator there:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
[activityIndicator stopAnimating];
}
But you may want to look into whether it's possible to reduce the time it takes to add the annotations in the first place since 30 seconds is a long time. Maybe you could only add the annotations that would be visible in the current region.

Related

User location appearing when setshowsuserlocation = NO

I created a custom view for user location using :
-(MKAnnotationView *)mapView:(MKMapView *)mV viewForAnnotation:(id <MKAnnotation>)annotation
{
if([annotation class]==[MKUserLocation class])
{
//my code to return the pin
}
else
{
//code for custom pin
}
After some user action i update setShowsUserLocation to NO for the mapview. Then I start core location controller which uses a different custom pin view to track user location. THe original pin dissappears for sometime but then reappears later on when i drag or pinch to zoom on the map. while debugging i saw that it enters the if condition above was called inspite of setShowsUserLocation being set to no.

User Location Strange Issue

I have all concept of Maps how it works but I am stuck in very strange issue.
As soon as open my Map Controller my default blue is visible (MKUserLocation) but when I am loading custom pins (IVMyLocation, Annotation class), default pin disappear.
I am properly managing removing of custom pins so that default pin shouldn't disappear like
for (id<MKAnnotation> annotation in _mapView.annotations) {
if([annotation isKindOfClass:[IVMyLocation class]])
[_mapView removeAnnotation:annotation];
}
But still my default pin is disappearing. Its only visible first time.
NOTE: Its working fine in Xcode4 simulator its disappearing in only device
You should return nil for the MKUserLocation in mapView:viewForAnnotation:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id < MKAnnotation >)annotation
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
// etc
This allows the default blue dot to show.
Reduce your objects count. Helped for me.

MapKit Find User Only When Map Loads

I'm hoping someone can point me to a convenient delegate method to answer the following question.
My question is: How do I know that the user has been located by MapKit for the first time?
When my view appears, I'd tell my MKMapView locate the user and set the map region around the user. After initially finding the user, I don't want to keep updating the map to his/her location. I want to give the user the freedom to pan around the map and not get automatically taken back to their location.
I'm having trouble when there's a delay in locating the user (for example, the very first time the user opens the app, MapKit doesn't locate the user until the he/she agrees to share their location). The result is that the map opens up somewhere over the Atlantic Ocean, and it doesn't correct itself once the user is found.
Unfortunately, -(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation gets called a couple times before the user's real location has been determined (When testing, I see its NSLog statements before I ever agree to share my location).
- (void)viewDidLoad
{
[super viewDidLoad];
self.mapView.delegate=self;
}
-(void)viewWillAppear:(BOOL)animated{
[self.mapView setShowsUserLocation:YES];
[self.mapView setUserTrackingMode: MKUserTrackingModeNone];
hasUpdatedRegion=NO;
}
//MapKit Delegate Methods
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
NSLog(#"didUpdateUserLocation");
if(!hasUpdatedRegion)
{
hasUpdatedRegion=YES;
MKCoordinateSpan span=MKCoordinateSpanMake(0.3, 0.3);
MKCoordinateRegion currentRegion=MKCoordinateRegionMake(mapView.userLocation.coordinate, span);
[mapView setRegion:currentRegion];
}
}
-(void)mapView:(MKMapView *)mapView didChangeUserTrackingMode:(MKUserTrackingMode)mode animated:(BOOL)animated{
NSLog(#"didChangeTrackingMode");
}
I have the same problem. Made another way...
- (void)viewDidLoad
{
self.mapView.userTrackingMode = YES;
}
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
self.mapView.userTrackingMode = NO;
}
First, set userTrakingMode to YES track the user as s/he moves.
Second, after updating map to user location didUpdateUserLocation is fired, when I've disabled the tracking mode.
This stops the map from tracking the user any more.
Why don't you , try setting the mapview region , so that the whole world map shows instead of a particular location(as u said 'Atantic ocean'), in case of user didn't allow the mapkit to use current location(i mean in view did load method ,set the map region .),,and if he allows..then set the region as per user's coordinates,.

Placing a blue dot (user location) Only using CLLocationManager

I've read through countless posts here on stack and apple's docs and can't find anything to solve this problem.
The issue is if you set mapView.showsUserLocation = YES, then MapKit will start making it's own GPS queries to your phone.
From apple docs:
Setting this property to YES causes
the map view to use the Core Location
framework to find the current
location. As long as this property is
YES, the map view continues to track
the user’s location and update it
periodically.
If you also want to use CLLocationManager, then when you make a call to [mylocationmanager startUpdatingLocation], then you are making a second GPS query on your phone.
Now you have 2 separate processes asking for GPS location.
Not a problem on the simulator, but if you try it on a real phone it will take a very very long time to get the GPS location. It is also inconsistent 10seconds - 1 minute, whereas if you turn off mapView.showsUserLocation it takes 2-3 seconds very consistently.
In general it seems like a very bad practice to use both.
For flexibility and control, I'd rather use CLLocationManager, but if you don't set mapView.showsUserLocation = YES, then you don't get the blue dot!
I tried the usual overwrite annotation methods: eg:
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation
{
if ([annotation isKindOfClass:MKUserLocation.class]) {
//it's the built-in user location annotation, return nil to get default blue dot...
return nil;
}
//handle your custom annotations...
}
But it doesn't work, most probably because there is never a call to actually place a user annotation on the map.
So does anyone have a solution to only use CLLocationManager to place the user's location on the map?
Just overriding the viewForAnnotation method isn't enough, you first have to add you annotations to the map by calling
[mapView addAnnotation:annotationObject];
Your annotationObject can be an instance of any class that implements the MKAnnotation protocol. You can find the details in the MapKit Guide in the Annotating apps section.
If you need to show just blue dot around your location (accuracy), you can do it like this:
MKCircle *accuracyCircle;
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
// when you want update your position and accuracy
[self.mapView removeOverlay:accuracyCircle];
accuracyCircle = [MKCircle circleWithCenterCoordinate:newLocation.coordinate
radius:newLocation.horizontalAccuracy];
[self.mapView addOverlay:accuracyCircle];
}
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
if([overlay isKindOfClass:[MKCircle class]])
{
MKCircleRenderer * circleRenderer = [[MKCircleRenderer alloc] initWithOverlay:overlay];
circleRenderer.fillColor = [UIColor colorWithRed:0 green:1 blue:0 alpha:0.2];
return circleRenderer;
}
return nil;
}

How to trigger MKAnnotationView's callout view without touching the pin?

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];