Custom pin with alphanumeric representation in MapKit - ios

I am now at a point of teaching myself the use of MapKit in Objective C. I got to the point where I can autozoom to a location that contains several annotations. I represent the annotations with the built in pins. If you click on the pin I have a an alphanumeric 2 character string to represent that spot.
I thought to myself, for better usability, why not replace the pins with the actual data. Kind of like a weathermap where they show the temperature as a pin. Is this doable?
I researched this and all I could find is this:
- (MKAnnotationView *)mapView:(MKMapView *)map viewForAnnotation:(id <MKAnnotation>)annotation
{
static NSString *AnnotationViewID = #"annotationViewID";
MKAnnotationView *annotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationViewID];
if (annotationView == nil) {
annotationView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationViewID] autorelease];
}
annotationView.image = [UIImage imageNamed:#"location.png"];
annotationView.annotation = annotation;
return annotationView;
}
The problem is that I cannot and should not have to have a custom image for each combination of the two characters. Is there a way for me to draw these numbers at the location of the pin.
I found this reference:
https://developer.apple.com/library/ios/documentation/userexperience/conceptual/LocationAwarenessPG/AnnotatingMaps/AnnotatingMaps.html
Am I on the right track. Are there some full examples I can leverage to better understand the flow.
I want to be able to select that custom pin however and segue or show more details.
Thank you for your time.

Anna, thank you for the link, I tried it and it worked. I made some changes to improve the esthetic of the label and I thought I would post my findings here for reference:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString *reuseId = #"MapViewController";
MKAnnotationView *av = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (av == nil)
{
av = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 20)];
//UILabel *lbl = [[UILabel alloc] init];;
lbl.backgroundColor = [UIColor clearColor]; // This makes the background clear and just shows the text
lbl.textColor = [UIColor whiteColor]; // Use any colour you wish
lbl.alpha = 1.0; //0.5;
lbl.tag = 42;
lbl.font = [UIFont fontWithName:#"Helvetica-Bold" size:16.0]; // This is optional, I found this fond and size most readable
[av addSubview:lbl];
//Following lets the callout still work if you tap on the label...
av.canShowCallout = YES;
av.frame = lbl.frame;
} else {
av.annotation = annotation;
}
UILabel *lbl = (UILabel *)[av viewWithTag:42];
// I added this to the text to improve its visibility by essentially adding a stroke around the text. Well its a poor man's stroke by adding a shadow
lbl.layer.shadowColor = [[UIColor blackColor] CGColor];
lbl.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
lbl.layer.shadowOpacity = 1.0f;
lbl.layer.shadowRadius = 1.0f;
lbl.text = annotation.title;
return av;
}

Related

MKMapView with custom pins return to red standard pins on reload

I have an MKMapView that I add pins to. They load correctly with their relevant graphics but if I zoom in and zoom back out they loose their graphic and turn into a standard red pin with the only customisation being the pin name (even my disclosure indicator disappear).
So far to try and fix it I've tried:
Tried png’s, checked on faster device, Changed everything from MKPinAnnotation to MKAnnotation, returning to a normal MKAnnotation instead of my custom CBAnnotation, Various sample codes for loading custom pins, Lowered quality of map overlay in case it was a loading issue but still an issue.
- (void)addPins {
mapPinsArray = [[NSMutableArray alloc] init];
for (MapPoint *mappoint in mapPointsArray) {
CBAnnotation *annotation = [[CBAnnotation alloc] init];
annotation.coordinate = CLLocationCoordinate2DMake(mappoint.loclat, mappoint.loclong);
annotation.title = mappoint.stopAreaName;
annotation.mapPoint = mappoint;
[mapPinsArray addObject:annotation];
[self.myMapView addAnnotation:annotation];
}
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(CBAnnotation *)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]]) {
//do nothing
return nil;
} else {
MKAnnotationView *annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"trailPoint"];
annotationView.canShowCallout = YES;
ButtonWithData *accessoryViewButton = [[ButtonWithData alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
[accessoryViewButton setBackgroundImage:[UIImage imageNamed:#"right_arrow"] forState:UIControlStateNormal];
accessoryViewButton.buttonData = annotation.mapPoint;
[accessoryViewButton addTarget:self action:#selector(disclosureButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
annotationView.rightCalloutAccessoryView = accessoryViewButton;
if (![[NSUserDefaults standardUserDefaults] boolForKey:annotation.mapPoint.stopAnimalName]) {
annotationView.image = [annotation.mapPoint lockedPinImage];
} else {
annotationView.image = [annotation.mapPoint unlockedPinImage];
}
return annotationView;
}
}
Fixed my own problem. I subclassed MKMapView to GenericMapView so that my code was cleaner (removing showsUserLocation, zoom/scroll/rotateEnabled, showsCompass etc. from the actual View Controller) but this meant the delegate wasn't setting correctly and viewForAnnotation wasn't being called on the pins reload.

How to add custom transparent MKAnnotation to MKMapView?

So I am trying to replicate the following scenario (translucent annotation views) :
And I have tried unsuccessfully the following implementations:
1- Creating a custom image with 30% opacity and adding to the map ---> Result: The image stays opaque.
Code:
-(id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self) {
LLAnnotation *myA = (LLAnnotation*) annotation;
self.accessibilityLabel = myA.title;
self.annotation = myA;
self.enabled = YES;
self.canShowCallout = YES;
self.centerOffset = CGPointMake(5,-10);
self.backgroundColor = [UIColor yellowColor];
self.image = [UIImage imageNamed:#"circle"];
}
return self;
}`
And then adding it in - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation_
2- Adding a sublayer to the AnnotationView and clearing it ---> Result: Doesn't show any annotation.
Code:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation_
{
if (annotation_ == mapView.userLocation) return nil;
MKAnnotationView *m = [[MKAnnotationView alloc] initWithAnnotation:annotation_ reuseIdentifier:#"default"];
// m.backgroundColor = [UIColor clearColor];
CALayer *layer = [[CALayer alloc]init];
layer.frame = m.frame;
layer.backgroundColor = [UIColor lightGreenColor].CGColor;
[m.layer addSublayer:layer];
m.layer.cornerRadius = m.frame.size.width/2;
m.layer.borderWidth = 2;
m.layer.masksToBounds = YES;
return m;
}
I was thinking that adding MKOverlays on top of annotations maybe a workaround but it shouldn't be the way to go I believe.
Does anyone have other suggestions on how to implement this?
Create UIImageView object and make it looks like the image you required.
Add as subview of annotationView in viewForAnnotation delegate method will do the trick.
Also you need to set center position offset for annotation image to render annotation exactly correct position of location.
Have look on below code:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation_
{
if (annotation_ == mapView.userLocation) return nil;
MKAnnotationView *m = [[MKAnnotationView alloc] initWithAnnotation:annotation_ reuseIdentifier:#"default"];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(m.center.x, m.center.y, 20, 20)];
[imageView setBackgroundColor:[UIColor colorWithRed:0.7294 green:0.7843 blue:0.1921 alpha:1.0]];
imageView.layer.cornerRadius = imageView.frame.size.width / 2;
imageView.alpha = 0.6f;
[m addSubview:imageView];
// Also set center offset for annotation
[m setCenterOffset:CGPointMake(-10, -20)];
return m;
}
What I would do is create an image in photoshop which has a transparent background, and then add your desired yellow circle on top. Then make the opacity of that circle to what ever opacity you want. Save the image as a PNG.
Once you have saved the image, add it to your Xcode project. Once you've added it, add the following line under your viewForAnnotation.
annotationView.image = [UIImage imageNamed:#"ThisIsThePNGImagesName.png"];
Hope that helps :)

UIImageView as callout's leftCalloutAccessoryView automatically shifts top-left corner

It works great on iOS 7 but UIImageView's position shifts on iO8 like images below. Also rightCalloutAccessoryView position shifts top-right corner.
Can anybody help?
Cheers.
In iOS 7
In iOS 8
-(void)mapView:(MKMapView*)mapView didSelectAnnotationView:(MKAnnotationView*)view
{
if(![view.annotation isKindOfClass:[MKUserLocation class]]) {
UBAnnotation *selectedAnnotation = view.annotation;
NSURL * imageUrlAtIndex = [NSURL URLWithString:[[self.objects objectAtIndex:selectedAnnotation.idx] ad_thumbImageUrl]];
UIImageView * leftCalloutView = [[UIImageView alloc] initWithFrame:CGRectMake(2, 2, 40, 40)];
[leftCalloutView setImageWithURL:imageUrlAtIndex];
leftCalloutView.layer.masksToBounds = YES;
leftCalloutView.layer.cornerRadius = 6;
view.leftCalloutAccessoryView = leftCalloutView;
}
}
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id ) annotation {
MKPinAnnotationView *annView=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"currentloc"];
if ([annotation isKindOfClass:[MKUserLocation class]]) {
return nil;
} else {
annView.image = [UIImage imageNamed:#"pin"];
annView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annView.rightCalloutAccessoryView.tintColor = [UBColor ubGreenColor];
annView.animatesDrop= NO;
annView.canShowCallout = YES;
}
return annView;
}
I solved it!
It's all about titleLabel's string length. If the string length over 20-25 character, left and right accessory view shifts up.
First, I trimmed the string that shown on title label or subtitle label. After, concatenate "..." as string at the end of the string.
Solution is a little bit hack, but it works like charm.
I made fix like this. Needs to polish the code, but for me it works.
- (UIView*)rightCalloutAccessoryView {
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
return [self parentViewForView:rightButton];
}
- (UIView*)parentViewForView:(UIView*)view {
#ifdef __IPHONE_8_0
CGRect rr = view.frame;
rr.origin.x = 2.0f;
rr.origin.y = (64.0f - rr.size.height ) / 2.0f;
view.frame = rr;
UIView *result = [[UIView alloc] initWithFrame:CGRectMake(0, 0, rr.size.width + 4.0f, 64.0f)];
[result addSubview:view];
return result;
#else
return view;
#endif
}
If you want to customize the leftCalloutAccessoryView be implemented an customizedView that you have to refine the width been under 225.0f ( the better setup is 220.0f ) on iPhone 4, 4S, 5, 5+.
for example :
UIView *_customContentView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 225.0f, 38.0f)];
view.leftCalloutAccessoryView = _customContentView;
That's my solved experience.

Update UILabel-Subview of MKAnnotationview

I have a MapView with cluster annotations (ADMapCluster). I want to show the amount of elements in the Cluster, therefor I'm adding a UILabel to the MKAnnotationView as a Subview.
My Problem is that when I reuse the MKAnnotationView after zooming or
similar actions the UILabel doesn't update the text.
- (MKAnnotationView *)mapView:(ADClusterMapView *)mapView viewForClusterAnnotation:(id<MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
MKAnnotationView * pinView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"ADMapCluster"];
if (!pinView) {
pinView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"ADMapCluster"];
pinView.image = [UIImage imageNamed:#"map-markers"];
UILabel *countLabel = [[UILabel alloc] initWithFrame:CGRectMake(2, 5, 25, 20)];
countLabel.textAlignment = NSTextAlignmentCenter;
[countLabel setTag:2];
countLabel.text = [NSString stringWithFormat:#"%d",[[(ADClusterAnnotation*)pinView.annotation originalAnnotations] count] ];
countLabel.font = [UIFont fontWithName:#"Avenir-Book" size:10];
[pinView addSubview:countLabel];
}
else {
pinView.annotation = annotation;
[((UILabel*)[pinView viewWithTag:2]) setText:[NSString stringWithFormat:#"%d",[[(ADClusterAnnotation*)pinView.annotation originalAnnotations] count] ]];
[((UILabel*)[pinView viewWithTag:2]) setNeedsDisplay];
}
return pinView;
}
Any idea what I'm doing wrong and why the labels don't get updated?

customizing map overlays in Xcode

I have a mapView with 2 circles. When I try to customize the large one the small one seems to follow the large one. For example if I customize the fill color for the large one the smaller one gets the same color. How do I make the smaller one a different color? Note: I use reusable identifiers. Thank you.. this is my working code but when i try to edit my smaller circle, the one with radius 100 it doesn't. note: this is my WORKING code as anything else i tried it failed.
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay {
static NSString *CircleOverlayIdentifier = #"Circle";
_mapView.delegate = self;
if ([overlay isKindOfClass:[CircleOverlay class]]) {
CircleOverlay *circleOverlay = (CircleOverlay *)overlay;
MKCircleView *annotationView =
(MKCircleView *)[mapView dequeueReusableAnnotationViewWithIdentifier:CircleOverlayIdentifier];
if (!annotationView) {
MKCircle *circle = [MKCircle
circleWithCenterCoordinate:circleOverlay.coordinate
radius:circleOverlay.radius];
annotationView = [[MKCircleView alloc] initWithCircle:circle];
//this one
}
if (overlay == self.targetOverlay) {
//adjustable
annotationView.fillColor = [UIColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:0.3f];
annotationView.strokeColor = [UIColor redColor];
annotationView.lineWidth = 1.0f;
} else {
//fixed
annotationView.fillColor = [UIColor colorWithWhite:0.3f alpha:0.3f];
annotationView.strokeColor = [UIColor purpleColor];
annotationView.lineWidth = 2.0f;
}
return annotationView;
}
return nil;
}
- (void)configureOverlay {
if (self.location) {
[self.mapView removeAnnotations:[self.mapView annotations]];
[self.mapView removeOverlays:[self.mapView overlays]];
CircleOverlay *overlaysmall = [[CircleOverlay alloc] initWithCoordinate:self.location.coordinate radius:100];
[self.mapView addOverlay:overlaysmall];
_targetOverlaySmall = overlaysmall;
CircleOverlay *overlay = [[CircleOverlay alloc] initWithCoordinate:self.location.coordinate radius:self.radius];
[self.mapView addOverlay:overlay];
GeoQueryAnnotation *annotation = [[GeoQueryAnnotation alloc] initWithCoordinate:self.location.coordinate radius:self.radius];
[self.mapView addAnnotation:annotation];
[self updateLocations];
}
}

Resources