In the upcoming version 3.4 of the MapBox iOS SDK, it should be possible to create custom annotations for the user location.
In the regular Apple MapKit, we used to do this by creating an custom view in mapView: viewForAnnotation.
Is the approach to this different with MapBox? We identify the annotations by the class they are based on. I assume that we would expect this delegate method to be called also for class MGLUserLocation?
-(MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id<MGLAnnotation>)annotation
{
if ([annotation isKindOfClass:[MGLUserLocation class]])
{
NSLog(#"MGLUserLocation"); // No hit
}
return nil;
}
Related
In my project I'm adding multiple annotations, but I need to remove only one annotation.
Is there any way I can specify an annotation?
I have tried to use dynamic variable during the creation, but it did not work.
This might be worked
-(void) mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
// Do other stuff
annotationToRemove = view.annotation;
}
You can also implement the didDeselectAnnotaionView method as well.
for reference check this Annotations
I'm learning to use MapKit in my fledgling iOS app. I'm using some of my model entities as annotations (added the <MKAnnotation> protocol to their header file). I also create custom MKAnnotationViews and set the draggable property to YES.
My model object has a location property, which is a CLLocation*. To conform to the <MKAnnotation> protocol, I added the following to that object:
- (CLLocationCoordinate2D) coordinate {
return self.location.coordinate;
}
- (void) setCoordinate:(CLLocationCoordinate2D)newCoordinate {
CLLocation* newLocation = [[CLLocation alloc]
initWithCoordinate: newCoordinate
altitude: self.location.altitude
horizontalAccuracy: self.location.horizontalAccuracy
verticalAccuracy: self.location.verticalAccuracy
timestamp: nil];
self.location = newLocation;
}
- (NSString*) title {
return self.name;
}
- (NSString*) subtitle {
return self.serialID;
}
So, I have the 4 required methods. And they're pretty straightforward. When I read the apple docs on MKAnnotationView and the #draggable property, it says the following:
Setting this property to YES makes an annotation draggable by the user. If YES, the associated annotation object must also implement the setCoordinate: method. The default value of this property is NO.
And elsewhere, the MKAnnotation docs say:
Your implementation of this property must be key-value observing (KVO) compliant. For more information on how to implement support for KVO, see Key-Value Observing Programming Guide.
I have read that (brief) document, and it is not clear at all to me what I'm supposed to do to accomplish that so that the coordinate, which I'm deriving from my location property is a proper property in and of itself.
But I'm reasonably sure it's not working correctly. When I drag the pin, it moves, but then it no longer relocates when I pan the map.
UPDATE
So I tried playing with the stock MKPinAnnotationView. To do this, I simply commented out my delegate's mapView:viewForAnnotation: method. I discovered that these aren't draggable by default. I added the mapView:didAddAnnotationViews: to my delegate to set the draggable property of the added views to YES.
Once configured thus, the Pin views, as hinted by John Estropia below, seem to work fine. I decided to use the mapView:annotationView:didChangeDragState:fromOldState: delegate hook to get a closer look at what is going on:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)annotationView didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState {
NSArray* states = #[#"None", #"Starting", #"Dragging", #"Cancelling", #"Ending"];
NSLog(#"dragStateChangeFrom: %# to: %#", states[oldState], states[newState]);
}
For the stock pins, one will see log output that looks like this:
2014-02-05 09:07:45.924 myValve[1781:60b] dragStateChangeFrom: None to: Starting
2014-02-05 09:07:46.249 myValve[1781:60b] dragStateChangeFrom: Starting to: Dragging
2014-02-05 09:07:47.601 myValve[1781:60b] dragStateChangeFrom: Dragging to: Ending
2014-02-05 09:07:48.006 myValve[1781:60b] dragStateChangeFrom: Ending to: None
Which looks pretty logical. But if you switch to the configured MKAnnotationView, the output you will see looks like:
2014-02-05 09:09:41.389 myValve[1791:60b] dragStateChangeFrom: None to: Starting
2014-02-05 09:09:45.451 myValve[1791:60b] dragStateChangeFrom: Starting to: Ending
It misses TWO transitions, from Starting to Dragging, and from Ending to None.
So I begin to be skeptical that I need to do something different with properties. But I'm still frustrated with why this won't work.
UPDATE 2
I created my own Annotation object to stand between my model objects, which could have a property coordinate property. The behavior remains the same. It seems to be something with the MKAnnotationView.
There are lots of examples about how to use the delegate method mapView:viewForAnnotation: that show setting up an MKAnnotationView. But what is not as obvious is that just because you set the draggable property of your MKAnnotationView instance to YES, you still have to write some code to help it transition some of the states. MapKit will take care of moving your instance's dragState to MKAnnotationViewDragStateStarting and MKAnnotationViewDragStateEnding, but it will not do the other transitions. You see hints of this in the docs notes about subclassing MKAnnotationView and the need to override the `setDragState:animated:'
When the drag state changes to MKAnnotationViewDragStateStarting, set the state to MKAnnotationViewDragStateDragging. If you perform an animation to indicate the beginning of a drag, and the animated parameter is YES, perform that animation before changing the state.
When the state changes to either MKAnnotationViewDragStateCanceling or MKAnnotationViewDragStateEnding, set the state to MKAnnotationViewDragStateNone. If you perform an animation at the end of a drag, and the animated parameter is YES, you should perform that animation before changing the state.
In this case, I'm not subclassing, but it seems that MKAnnotationView still struggles to make the transitions on its own. So you have to implement the delegate's mapView:annotationView:didChangeDragState:fromOldState: method. E.g.
- (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;}
}
}
This allows things to complete appropriately, so that when you pan the map after dragging the annotation, the annotation moves with the pan.
Are you using a custom map pin? I saw this before as well. Seems to be a bug in iOS 7. As a workaround, we just ended up using the default pin for MKPinAnnotationView.
Your setCoordinate was missing the required Key-Value observing methods (willChangeValueForKey/didChangeValueForKey) that are required for the map to detect when the annotation has been moved so it can move the annotation view to match, e.g.
- (void) setCoordinate:(CLLocationCoordinate2D)newCoordinate {
[self willChangeValueForKey:#"coordinate"];
CLLocation* newLocation = [[CLLocation alloc]
initWithCoordinate: newCoordinate
altitude: self.location.altitude
horizontalAccuracy: self.location.horizontalAccuracy
verticalAccuracy: self.location.verticalAccuracy
timestamp: nil];
self.location = newLocation;
[self didChangeValueForKey:#"coordinate"];
}
Source:
https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKAnnotation_Protocol/index.html#//apple_ref/occ/intfp/MKAnnotation/coordinate
"Your implementation of this property must be key-value observing (KVO) compliant. For more information on how to implement support for KVO, see Key-Value Observing Programming Guide."
Sorry Apple wasn't clear in the docs, what they meant to say was the setCoordinate requires the Key-Value observing methods willChangeValueForKey & didChangeValueForKey that allow the map to detect when the annotation has been moved so it can move the annotation view to match, e.g.
- (void) setCoordinate:(CLLocationCoordinate2D)newCoordinate {
[self willChangeValueForKey:#"coordinate"];
CLLocation* newLocation = [[CLLocation alloc]
initWithCoordinate: newCoordinate
altitude: self.location.altitude
horizontalAccuracy: self.location.horizontalAccuracy
verticalAccuracy: self.location.verticalAccuracy
timestamp: nil];
self.location = newLocation;
[self didChangeValueForKey:#"coordinate"];
}
In iOS, only one MKPointAnnotation's callout can be displayed at a time. I would like to be able to show all of the pins' callouts on the screen at once. Any recommendations would be appreciated.
Did you try this method of MKMapView Class:
- (void)addAnnotations:(NSArray *)annotations;
You should add all your MKAnnotation objects to an array and call the class method with the array.
Example:
NSMutableArray *filterLocs=[NSMutableArray array];
for (MKAnnotation *loc in YourMapPointsArray)
{
//add some condition here to manipulate your map points
[filterLocs addObject:loc];
}
[self.mapView addAnnotations:filterLocs];
or just make it plain
[self.mapView addAnnotations:YourMapPointsArray];
I started to build an app with iOS6 that worked great, but then for reasons of force grater, I had to switch to IOS5. However, there is a map that keeps giving me problems. This map has many types of annotationView (for example cinemas, restaurants, theaters ....), each with their own image. When I passed from iOS6 to iOS5 I noticed that the annotationView do not behave the same as before because the call of the delegate method to construct them is no longer the same. What can I do?
-(void)viewDidLoad{
//.....
//extraction of elements from the tables in a database
for(int i=0; i<7; i++){ //extraction tables from database
//......
for (int i =0; i< number; i++) { //extraction elements from tables
self.chinaTable =[self.mutableArray objectAtIndex:i];
CLLocationCoordinate2D coord=CLLocationCoordinate2DMake(self.chinaTable.latitudine, self.chinaTable.longitudine);
AnnotationCustom *annotationIcone =[[AnnotationCustom alloc]initWithCoordinates:coord title:self.chinaTable.titolo subTitle:self.chinaTable.indirizzo];
//.......
[self.mapView addAnnotation:annotation];
self.locationManager.delegate = self;
self.mapView.delegate=self; //the problem is here
}
}
The delegate method
- (MKAnnotationView *)mapView:(MKMapView *)mapview viewForAnnotation:(id
<MKAnnotation>)annotation
{
NSLog (#"the name of the table is %#", self.nomeTabella);
// this NSLog you get only the name of the last open table
//..........
if ([annotation isKindOfClass:[AnnotationCustom class]]){
static NSString* AnnotationIdentifier = #"AnnotationIdentifier";
MKAnnotationView *annotationView = (MKAnnotationView*) [mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
annotationView = [[MKAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
annotationView.annotation = annotation;
AnnotationCustom *customAnnotation = (AnnotationCustom *)annotationView.annotation;
//I create an annotation different depending on the name of the table of provenance
if ([self.nomeTabella isEqualToString:#"Cinema"]){
if(UI_USER_INTERFACE_IDIOM()== UIUserInterfaceIdiomPhone){
annotationView.image = [UIImage imageNamed:#"iphone_cinema.png"];
customAnnotation.nomeTabella = self.nomeTabella;
customAnnotation.ID = self.chinaTable.ID;
customAnnotation.china = self.chinaTable;
}else{
annotationView.image = [UIImage imageNamed:#"ipad_cinema.png"];
customAnnotation.nomeTabella = self.nomeTabella;
customAnnotation.ID = self.chinaTable.ID;
customAnnotation.china=self.chinaTable;
}
//......
}
The delegate method viewForAnnotation is no longer being called after the construction of each annotation, but is only called at the end of both cycles, accordingly annotationView on the map are only those of the last table in memory. Where can I get set i delegate methods to get the same result as before? ViewForAnnotation work fine in iPad 6.0 simulator but doesn't work in iPad simulator 5.1 and the code is the same
I'm surprised to hear that viewForAnnotation was only ever called after each construction. It's not the way it is documented and it is not the way it is used by anyone else. viewForAnnotation can and will be called at any point int your app. If the user scrolls the map so some annotations disappear it can be called when they scroll the map back and they annotations reappear. Or if the user switches to another view or app and then comes back.
You need to make viewForAnnotation check the annotation variable for some property that lets you determine how to draw that view. You can't rely on it being called in any particular order. There's plenty of sample code around that will show you how to implement the MKAnnotation protocol with your own class and you can add something to that class to tell you if the thing it represents is a cinema or restaurant or whatever, then you fetch the right image put it into the MKAnnotationView that viewForAnnotation wants as a return value.
viewForAnnotation isn't being called when you are running through your loops (i.e. as you add each MKAnnotation) because you are on the main runLoop. When your viewDidLoad finishes and a runLoop cycle occurs the MKAnnotations (those on the current map location) will be drawn on the screen, thus calling out to your delegate. So while you are debugging and stepping through viewDidLoad it is perfectly normal that you are not seeing calls to the delegate methods.
A couple of things:
You should be setting your delegate methods outside of the loops. so self.locationManager.delegate and self.mapView.delegate should be set before your for loops, not a million times inside your nested loops.
You are reusing i as your for...loop variable. I'm hoping this is just caused by you removing business logic for posting here, and not in your actual code. It would explain why only your last set of annotations appears to be rendered.
Are you sure you aren't manipulating the mapView.annotations elsewhere in the nested for...loops? Check for removeAnnotations and setAnnotations: both could cause the annotations in the mapView to be different from what you'd expect.
Can you show how your annotation object is constructed? Another possibility is that you're accidentally mutating existing objects instead of creating new ones.
your code seems ok to me. Actually I'm also developing an Iphone app using Maps and I'm doing it like you. Only one question, why are you using:
if(UI_USER_INTERFACE_IDIOM()== UIUserInterfaceIdiomPhone)
is it to check if user is running an iPad or an iPhone?
I've created a mapview which has a button to switch between an 'edit' mode and a 'drag' mode based on the project requirements. I realize that it's easy enough to have your annotations draggable from creation by setting them draggable in the viewForAnnotation, but the behavior required won't allow this. I've tried a couple different ways of changing the annotations to draggable without success. The first thought was to loop through the existing annotations and set each one to 'draggable' and 'selected', but I get an unrecognized selector sent to instance error (I did try instantiating a new annotation to pass in the object and re-plot while in the loop, but I get the same error as well):
NSLog(#"Array Size: %#", [NSString stringWithFormat:#"%i", [mapView.annotations count]]);
for(int index = 0; index < [mapView.annotations count]; index++) {
if([[mapView.annotations objectAtIndex:index]isKindOfClass:[locAnno class]]){
NSLog(#"** Location Annotation at Index: %#", [NSString stringWithFormat:#"%i", index]);
NSLog(#"* Location Marker: %#", [mapView.annotations objectAtIndex:index]);
}
if([[mapView.annotations objectAtIndex:index]isKindOfClass:[hydAnno class]]) {
NSLog(#"** Hydrant Annotation at Index: %#", [NSString stringWithFormat:#"%i", index]);
NSLog(#"* Hydrant Marker: %#", [mapView.annotations objectAtIndex:index]);
[[mapView.annotations objectAtIndex:index]setSelected:YES];
[[mapView.annotations objectAtIndex:index]setDraggable:YES];
}
}
The second thought was to use 'didSelectAnnotationView', and set selected and draggable on the annotation when it's selected, and reset the properties when the mode switches back again. This works, but very poorly as the event doesn't always fire and your left to tap the annotation one or more times before it will change the properties:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
NSLog(#"Annotation Selected!");
if(!editMode) {
view.selected = YES;
view.draggable = YES;
}
}
The first attempt seems the most simple solution if I can get it to work. Using the didSelect method on the other hand is cumbersome and hack-licious. I'm quite new to iOS development, so I apologize if I've overlooked something novice while hammering away at this. I appreciate any insight the community can offer. Thanks much.
The first method is better than using the didSelectAnnotationView delegate method.
The problem with the code which causes the "unrecognized selector" error is that it is calling setSelected: and setDraggable: on the annotation objects (type id<MKAnnotation>) instead of their corresponding MKAnnotationView objects. The id<MKAnnotation> objects don't have such methods so you get that "unrecognized selector" error.
The map view's annotations array contains references to the id<MKAnnotation> (data model) objects -- not the MKAnnotationView objects for those annotations.
So you need to change this:
[[mapView.annotations objectAtIndex:index]setSelected:YES];
[[mapView.annotations objectAtIndex:index]setDraggable:YES];
to something like this:
//Declare a short-named local var to refer to the current annotation...
id<MKAnnotation> ann = [mapView.annotations objectAtIndex:index];
//MKAnnotationView has a "selected" property but the docs say not to set
//it directly. Instead, call deselectAnnotation on the annotation...
[mapView deselectAnnotation:ann animated:NO];
//To update the draggable property on the annotation view, get the
//annotation's current view using the viewForAnnotation method...
MKAnnotationView *av = [mapView viewForAnnotation:ann];
av.draggable = editMode;
You must also update the code in the viewForAnnotation delegate method so that it also sets draggable to editMode instead of a hard-coded YES or NO so that if the map view needs to re-create the view for the annotation after you've already updated it in the for-loop, the annotation view will have the right value for draggable.