Map Annotations ars displaying with incorrect custom pin images - objective-c

This is my first question on this site.
My code is implemented in the following function using iOS 6 Mapkit, Objective-C.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
I have ONLY TWO annotations with custom pin images. One red pin and the other a orange pin. The red pin is closer to the user's current location within device map area and the other orange pin is 30 miles away (outside current map context). I am using different colors based on the data behind each pin.
Problem: Custom images for annotations are switching images.
Using all the tips on this site which includes usage of dequeueReusableAnnotationViewWithIdentifier, etc attached is my code.
On the very first display of the map on app launch, the error I see is that the orange pin displays on the device map and the red one is displayed outside. This is incorrect because the red image should have displayed on the device map.
If I tap the 'Find Me' button on the map to refresh my current location the red pin displays on the map which is now correct. When I tap 'Find Me' again, the red pin switches out to orange pin-- and it keeps switching or toggling pin image colors.
if (PinColor == 0) {
MKAnnotationView* pinview = (MKAnnotationView *)[self._mapView dequeueReusableAnnotationViewWithIdentifier:#"identifier"];
if (nil == pinview) {
MKAnnotationView* view = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"identifier"];
view.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
view.opaque = YES;
view.image = [UIImage imageNamed:#"redpin.png"];
[view setCanShowCallout:YES];
if (self.newPinAdded) {
[view setSelected: YES];
}
return view;
}
return pinview;
}
else {
MKAnnotationView* pinview = (MKAnnotationView *)[self._mapView dequeueReusableAnnotationViewWithIdentifier:#"Orangeidentifier"];
if (nil == pinview) {
MKAnnotationView* view = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"Orangeidentifier"];
view.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
view.image = [UIImage imageNamed:#"pin glabensmall.png"]; //orange pin
view.opaque = YES;
[view setCanShowCallout:YES];
if (self.newPinAdded) {
[view setSelected: YES];
}
return view;
}
return pinview;
}

I think your problem is a common one. Where is PinColor set? The mapview map ask for the annotationView to draw an annotation at any time for any annotation. If PinColor is set to 0 by some other method and then the mapview wants to draw an annotation any annotation it will draw a red pin. What you need to do is check which annotation you are drawing and then use the right colour for it. You can check the annotation by reading its title or if it is your own annotation class there may be some other property you can use.
SideNote: There are a few lines you've repeated for both pin version, you should have them outside the IF statement and cut out some lines. And you should use the mapView that viewForAnnotation give you:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id < MKAnnotation >)annotation{
MKAnnotationView* pinview = nil;
if (annotation is the red one) {
pinview = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"identifier"];
if (nil == pinview) {
pinview = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"identifier"];
pinview.image = [UIImage imageNamed:#"redpin.png"];
}
} else {
pinview = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"Orangeidentifier"];
if (nil == pinview) {
pinview = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"Orangeidentifier"];
pinview.image = [UIImage imageNamed:#"pin glabensmall.png"]; //orange pin
}
}
[pinview setCanShowCallout:YES];
pinview.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
pinview.opaque = YES;
if (self.newPinAdded) {
[pinview setSelected: YES];
}
}

Related

Custom Map Pin Image Objective-C

I am currently struggling to use a custom map pin image instead of the default coloured pins. I have tried some guides online but can’t seem to integrate it into my current set up.
Currently I have a certain coloured (tinted) pin set during certain times (online) and when the time is outside these parameters the pin turns a different colour.
I want to change these pins to two different custom pin images that I have created; one for online and one for offline.
I have read something about the MKPinAnnotationView not liking the pinView.image property (I have tried this and can confirm it doesn’t work).
If someone could edit this code so I can have a “online.png“ image for the annotation tag 0 and an “offline.png” image for the annotation tag 1, that would be perfect.
MapViewController.m
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
MKPinAnnotationView *pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"CustomPinAnnotationView"];
UIButton *advertButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
pinView.rightCalloutAccessoryView = advertButton;
pinView.canShowCallout = YES;
pinView.draggable = NO;
pinView.highlighted = YES;
if (annotation == mapView.userLocation)
return nil;
if (([(Annotation*)annotation tag] == 0) && [[(Annotation*)annotation area] isEqualToString:#"Online"])
{
pinView.pinTintColor = [UIColor colorWithRed:0.92 green:0.21 blue:0.21 alpha:1];
pinView.alpha = 1;
[advertButton addTarget:self action:#selector(Online:) forControlEvents:UIControlEventTouchUpInside];
}
else if (([(Annotation*)annotation tag] == 1) && [[(Annotation*)annotation area] isEqualToString:#"Offline"])
{
pinView.pinTintColor = [UIColor colorWithRed:0.8 green:0.8 blue:0.8 alpha:0.5];
pinView.alpha = 1;
[advertButton addTarget:self action:#selector(Offline:) forControlEvents:UIControlEventTouchUpInside];
}
return pinView;
}
Any help would be greatly appreciated; I am not a coder by nature so go easy.
Cheers
Lewis
If you want to display a pin as annotation, you use MKPinAnnotationView. If you want to use a custom annotation image, you had to use MKAnnotationView, and set the image property.
So I suggest to replace
MKPinAnnotationView *pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"CustomPinAnnotationView"];
by
MKAnnotationView *pinView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"CustomPinAnnotationView"];
and set the image property, e.g. using:
pinView.image = [UIImage imageNamed:#„xxx“];
Of course, you should rename pinView, CustomPinAnnotationView and xxx appropriately.

Render Title of MKPolygon

I'm trying to render MKPolygon using the following code:
NSMutableArray *overlays = [NSMutableArray array];
for (NSDictionary *state in states) {
NSArray *points = [state valueForKeyPath:#"point"];
NSInteger numberOfCoordinates = [points count];
CLLocationCoordinate2D *polygonPoints = malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D));
NSInteger index = 0;
for (NSDictionary *pointDict in points) {
polygonPoints[index] = CLLocationCoordinate2DMake([[pointDict valueForKeyPath:#"latitude"] floatValue], [[pointDict valueForKeyPath:#"longitude"] floatValue]);
index++;
}
MKPolygon *overlayPolygon = [MKPolygon polygonWithCoordinates:polygonPoints count:numberOfCoordinates];
overlayPolygon.title = [state valueForKey:#"name"];
[overlays addObject:overlayPolygon];
free(polygonPoints);
}
[self.stateMapView addOverlays:overlays];
I used the following code to provide stroke and fill colors:
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay NS_AVAILABLE(10_9, 7_0);
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygonRenderer *pv = [[MKPolygonRenderer alloc] initWithPolygon:overlay];
pv.fillColor = [UIColor redColor];
pv.strokeColor = [UIColor blackColor];
return pv;
}
return nil;
}
Do I need to do something to render the Title? I think I should enable a configuration or something but I'm new to MapView. Or I need to create a UILabel?
Overlays don't automatically show their titles like annotations can (in their callout actually) so there's nothing you "need to do" or any configuration that you can enable.
A simple workaround to show titles on overlays is, as you suggest, to create a UILabel.
However, this UILabel should be added to an annotation view that is positioned at each overlay's center.
A minor drawback (or maybe not) to this method is that the titles will not scale with the zoom of the map -- they'll stay the same size and can eventually collide and overlay with other titles (but you may be ok with this).
To implement this approach:
For each overlay, add an annotation (using addAnnotation: or addAnnotations:) and set the coordinate to the approximate center of the overlay and the title to the overlay's title.
Note that since MKPolygon implements both the MKOverlay and the MKAnnotation protocols, you don't necessarily need to create a separate annotation class or separate objects for each overlay. MKPolygon automatically sets its coordinate property to the approximate center of the polygon so you don't need to calculate anything. You can just add the overlay objects themselves as the annotations. That's how the example below does it.
Implement the mapView:viewForAnnotation: delegate method and create an MKAnnotationView with a UILabel in it that displays the title.
Example:
[self.stateMapView addOverlays:overlays];
//After adding the overlays as "overlays",
//also add them as "annotations"...
[self.stateMapView addAnnotations:overlays];
//Implement the viewForAnnotation delegate method...
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
{
//show default blue dot for user location...
return nil;
}
static NSString *reuseId = #"ann";
MKAnnotationView *av = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (av == nil)
{
av = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
av.canShowCallout = NO;
//add a UILabel in the view itself to show the title...
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 50, 30)];
titleLabel.tag = 42;
titleLabel.backgroundColor = [UIColor clearColor];
titleLabel.textColor = [UIColor blackColor];
titleLabel.font = [UIFont boldSystemFontOfSize:16];
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.minimumScaleFactor = 0.5;
[av addSubview:titleLabel];
av.frame = titleLabel.frame;
}
else
{
av.annotation = annotation;
}
//find the UILabel and set the title HERE
//so that it gets set whether we're re-using a view or not...
UILabel *titleLabel = (UILabel *)[av viewWithTag:42];
titleLabel.text = annotation.title;
return av;
}
The alternative approach is to create a custom overlay renderer and do all the drawing yourself (the polygon line, the stroke color, the fill color, and the text). See Draw text in circle overlay and Is there a way to add text using Paths Drawing for some ideas on how to implement that.

Map annotation not showing accessory view properly

I am adding some annotations to a MKMapView and these annotations have a UIImageView, loaded asynchronously with AFNetworking, as left callout accessory view. Here's the implementation:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
static NSString *identifier = #"MapAnnotation";
if ([annotation isKindOfClass:[MapAnnotation class]]) {
MKAnnotationView *annotationView = (MKAnnotationView *)[_mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
annotationView.image = [UIImage imageNamed:#"map_pin"];
UIImageView *userAvatar = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
[userAvatar setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=small",[[(MapAnnotation *)annotation user] facebookID]]] placeholderImage:[UIImage imageNamed:#"user_placeholder"]];
userAvatar.layer.cornerRadius = 4.0;
userAvatar.layer.masksToBounds = YES;
userAvatar.layer.borderColor = [[UIColor blackColor] CGColor];
userAvatar.layer.borderWidth = 1;
annotationView.leftCalloutAccessoryView = userAvatar;
UIButton *rightCallout = [UIButton buttonWithType:UIButtonTypeInfoLight];
annotationView.rightCalloutAccessoryView = rightCallout;
}
annotationView.annotation = annotation;
return annotationView;
}
return nil;
}
The problem here is: sometimes (not all the times) when annotations for different users are loaded into the map, they show the same image for all of them.
Let's say I have to load two annotations with this information:
Annotation 1
Name (title): Joshua
Datetime (subtitle): 19/06, 11:00
Image: www.myurl.com/joshua.jpg
Annotation 2
Name (title): Mathew
Datetime (subtitle): 10/06, 20:00
Image: www.myurl.com/mathew.jpg
Sometimes they are shown with title and subtitle correctly but the image loaded is the same for all of them (joshua's image or vice versa).
Am I missing something here is this method?
The call to dequeueReusableAnnotationViewWithIdentifier: will at the first call return nil which will satisfy annotationView == nil and set up your annotation view. So all the following calls to dequeueReusableAnnotationViewWithIdentifier: will return the first created annotation view with the assigned avatar image.
So [userAvatar setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=small",[[(MapAnnotation *)annotation user] facebookID]]] placeholderImage:[UIImage imageNamed:#"user_placeholder"]]; is only called once.
You need to move annotation specific changes to the annotation view outside the "if nil" setup scope.
So you should instead set the userAvatar image each time the mapview ask for a annotationView.
So something like this:
MKAnnotationView *annotationView = (MKAnnotationView *)[_mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
annotationView.image = [UIImage imageNamed:#"map_pin"];
UIImageView *userAvatar = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
userAvatar.layer.cornerRadius = 4.0;
userAvatar.layer.masksToBounds = YES;
userAvatar.layer.borderColor = [[UIColor blackColor] CGColor];
userAvatar.layer.borderWidth = 1;
annotationView.leftCalloutAccessoryView = userAvatar;
UIButton *rightCallout = [UIButton buttonWithType:UIButtonTypeInfoLight];
annotationView.rightCalloutAccessoryView = rightCallout;
}
[((UIImageView *)annotationView.leftCalloutAccessoryView) setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=small",[[(MapAnnotation *)annotation user] facebookID]]] placeholderImage:[UIImage imageNamed:#"user_placeholder"]];
This will assign the correct image each time the map view asks for an annotation view.
Cheers
Morten

How to connect map annotation view buttons with database to go to another view?

- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
//if it's user location, return nil
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
//try to dequeue an existing pin view first
static NSString* AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView* pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
pinView.animatesDrop = YES;
pinView.canShowCallout = YES;
pinView.pinColor = MKPinAnnotationColorRed;
//button on the right for popup for pins
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton setTitle:annotation.title forState:UIControlStateNormal];
[rightButton addTarget:self
action:#selector(showDetails:) forControlEvents:UIControlEventTouchUpInside];
pinView.rightCalloutAccessoryView = rightButton;
//zoom button on the left of popup for pins
UIButton* leftButton = [UIButton buttonWithType:UIButtonTypeContactAdd];
[leftButton setTitle:annotation.title forState:UIControlStateNormal];
[leftButton addTarget:self
action:#selector(zoomToLocation:) forControlEvents:UIControlEventTouchUpInside];
pinView.leftCalloutAccessoryView = leftButton;
return pinView;
}
//for map view annotation right button
-(void)showDetails:(id)sender{
NSLog(#"Annotation Click");
//fypAppDelegate *appDelegate = (fypAppDelegate *)[[UIApplication sharedApplication] delegate];
//Attraction *attraction = (Attraction *)[appDelegate.attractions objectAtIndex:sender];
infoViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"info"];
self.infoView = viewController;
[self.navigationController pushViewController:infoView animated:true];
}
//for map view annotation left button
-(void)zoomToLocation:(id)sender{
NSLog(#"Annotation Click");
}
Above is the delegate for the map annotations. I am able to show the pins and show the map annotation view but I don't know how to link the button events to the next view (infoViewController).
So as you guys can see, the right button is the one I want to use to enable user to view more information about that place while the left button, I want to allow user to zoom in into the coordinates of that pin.
The data are from the database I've created. Below is how I did it just for reference (in case you guys might need it)
-(void)putPins
{
fypAppDelegate *appDelegate = (fypAppDelegate *)[[UIApplication sharedApplication] delegate]; //get data
[appDelegate readTopAttractions];
int i = 0;
int count = appDelegate.attractions.count;
self.mapAnnotations = [[NSMutableArray alloc] initWithCapacity:appDelegate.attractions.count];
while (i < count) {
Attraction *attraction = (Attraction *)[appDelegate.attractions objectAtIndex:i];
i++;
//Set coordinates for pin
CLLocationCoordinate2D location;
location.latitude = (double)[[attraction xCoor] doubleValue];
location.longitude = (double)[[attraction yCoor] doubleValue];
MapPin *mapPin = [[MapPin alloc] init];
[mapPin setCoordinate:location];
[mapPin setName: [attraction name]];
NSString *desc = [attraction description];
int i = 0, position;
while(i < 50){
if ([desc characterAtIndex:i] == ' '){
position = i;
i++;
}
else
i++;
}
desc = [#"" stringByAppendingFormat:#"%#%#", [desc substringToIndex:position], #"..."];
[mapPin setDescription: desc];
[self.mapAnnotations addObject:mapPin];
}
[self.mapView addAnnotations:self.mapAnnotations];
}
please do tell me if you guys need more details.
Thank you! =)
In your showDetails: and zoomToLocation: methods, you can get a reference to the annotation whose callout button was tapped by doing the following:
MapPin *ann = (MapPin *)[mapView.selectedAnnotations objectAtIndex:0];
In zoomToLocation: you can then zoom in to that annotation using:
[mapView setRegion:
MKCoordinateRegionMakeWithDistance(ann.coordinate, 500, 500)
//500 meters vertical span, 500 meters horizontal span
animated:YES];
In showDetails:, you can pass ann or its properties to the detail view.
By the way, instead of calling custom methods using addTarget in viewForAnnotation, you can use the map view's calloutAccessoryControlTapped delegate method which gives more direct access to the annotation that was tapped. For example:
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control
{
MapPin *ann = (MapPin *)view.annotation;
if (control == view.rightCalloutAccessoryView)
{
NSLog(#"calloutAccessoryControlTapped: control=RIGHT");
//show detail view (or you can call your custom method here)...
}
else
if (control == view.leftCalloutAccessoryView)
{
NSLog(#"calloutAccessoryControlTapped: control=LEFT");
//zoom in (or you can call your custom method here)...
}
else
{
NSLog(#"calloutAccessoryControlTapped: unknown control");
}
}
Make sure you remove the addTarget calls from viewForAnnotation if you decide to use the calloutAccessoryControlTapped delegate method.
You want to zoom in to the particular pin? Is that right?
Therefore you can use the setRegion:animated: - method from MKMapView.
Example:
mapView = MKMapView
location = CLLLocationCoordinate2D
METERS_PER_MILE = 1609.344 (defined as a Constant)
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(location, 0.5*METERS_PER_MILE, 0.5*METERS_PER_MILE);
MKCoordinateRegion adjustedRegion = [mapView regionThatFits:region];
[mapView setRegion:adjustedRegion animated:YES];
AppleDocs
http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKMapView_Class/MKMapView/MKMapView.html
http://developer.apple.com/library/IOs/#documentation/MapKit/Reference/MapKitDataTypesReference/Reference/reference.html

Annotation Image is replaced by RedPushPin when long press on annotation

I have created Custom Annotation with following:
-(MKAnnotationView*)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
MKPinAnnotationView *view = nil;
if (annotation != mapView.userLocation)
{
view = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"myAnnotationIdentifier"];
if (!view)
view = [[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:#"myAnnotationIdentifier"];
if (((CustomAnnotation *)annotation).annotationType == 1)
{
view.image = [UIImage imageNamed:#"type1.png"];
view.rightCalloutAccessoryView = nil;
view.canShowCallout = YES;
}
else
{
view.image = [UIImage imageNamed:#"type2.png"];
view.rightCalloutAccessoryView = nil;
view.canShowCallout = YES;
}
}
return view;
}
Problem: When User press and hold for 2 seconds on any Annotation Image (type1 or type2), Image gets replaced by Red PushPin(Default for iPhone MKPinAnnotationView).
I want to avoid this replacement. How can I do so?
Instead of declaring and creating an MKPinAnnotationView, declare and create a plain MKAnnotationView.
The MKPinAnnotationView likes to default to the red pin which is what it's for.
Use didDeselectAnnotationView and didSelectAnnotationView and reselect the image as you did by :-
view.image = [UIImage imageNamed:#"type2.png"];