iOS Custom Annotation: A view below the annotation pin
Asked Answered
R

1

3

I need to replace the default annotation view with my custom annotation view.

I need the do following things:

  1. Custom Annotation view with an image view embedded in it.
  2. A view below it which contains a label in it.

For more clarification see the image:

Sample Pin

In the above image I need to place an image view in the white space which you can see in the image in circular form, next I also need to add a view which contains a label on which I can set any text like me, friends, etc...

So, for this I searched number of questions on stack overflow but didn't got my answer. I don't want it on call out, I just want it simply as annotation when map is rendered. I have tried to make a custom class for this but not getting any idea how to deal with this.

Any help will be highly appreciated

Rickyrico answered 22/5, 2015 at 12:28 Comment(7)
Take the image and add a text on it using the following url, #6993330 and then use that output image as a annotation pin.Lechner
How is that question linked with custom annotation?Rickyrico
you want to create image with text right?Lechner
Have a look at the image I have uploaded I need to create a custom annotation, with a custom image in it, with a custom view below it which contains a labelRickyrico
you are talking about pin or callout view?Lechner
I am talking about the pin not the callout viewRickyrico
Let us continue this discussion in chat.Lechner
Q
7

You could just create your own annotation view:

@import MapKit;

@interface CustomAnnotationView : MKAnnotationView

@end

@interface CustomAnnotationView ()
@property (nonatomic) CGSize textSize;
@property (nonatomic) CGSize textBubbleSize;
@property (nonatomic, weak) UILabel *label;
@property (nonatomic) CGFloat lineWidth;
@property (nonatomic) CGFloat pinRadius;
@property (nonatomic) CGFloat pinHeight;

@property (nonatomic, strong) UIBezierPath *pinPath;
@property (nonatomic, strong) UIBezierPath *textBubblePath;
@end

@implementation CustomAnnotationView

- (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
    if (self) {
        self.lineWidth = 1.0;
        self.pinHeight = 40;
        self.pinRadius = 15;

        UILabel *label = [[UILabel alloc] init];
        label.textAlignment = NSTextAlignmentCenter;
        label.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCallout];
        label.textColor = [UIColor whiteColor];
        [self addSubview:label];
        self.label = label;

        [self adjustLabelWidth:annotation];

        self.opaque = false;
    }
    return self;
}

- (void)setAnnotation:(id<MKAnnotation>)annotation {
    [super setAnnotation:annotation];
    if (annotation) [self adjustLabelWidth:annotation];
}

- (void)adjustLabelWidth:(id<MKAnnotation>)annotation {
    NSString *title = [annotation title];
    NSDictionary *attributes = @{NSFontAttributeName : self.label.font};
    self.textSize = [title sizeWithAttributes:attributes];
    CGFloat delta = self.textSize.height * (1.0 - sinf(M_PI_4)) * 0.55;
    self.textBubbleSize = CGSizeMake(self.textSize.width + delta * 2, self.textSize.height + delta * 2);
    self.label.frame = CGRectMake(0, self.pinHeight, self.textBubbleSize.width, self.textBubbleSize.height);
    self.label.text = title;
    self.frame = CGRectMake(0, 0, self.textBubbleSize.width, self.pinHeight + self.textBubbleSize.height);
    self.centerOffset = CGPointMake(0, self.frame.size.height / 2.0 - self.pinHeight);
}

- (void)drawRect:(CGRect)rect {
    CGFloat radius = self.pinRadius - self.lineWidth / 2.0;
    CGPoint startPoint = CGPointMake(self.textBubbleSize.width / 2.0, self.pinHeight);
    CGPoint center = CGPointMake(self.textBubbleSize.width / 2, self.pinRadius);
    CGPoint nextPoint;

    // pin

    self.pinPath = [UIBezierPath bezierPath];
    [self.pinPath moveToPoint:startPoint];
    nextPoint = CGPointMake(self.textBubbleSize.width / 2 - radius, self.pinRadius);
    [self.pinPath addCurveToPoint:nextPoint
                    controlPoint1:CGPointMake(startPoint.x, startPoint.y - (startPoint.y - nextPoint.y) / 2.0)
                    controlPoint2:CGPointMake(nextPoint.x, nextPoint.y + (startPoint.y - nextPoint.y) / 2.0)];

    [self.pinPath addArcWithCenter:center radius:radius startAngle:M_PI endAngle:0 clockwise:TRUE];
    nextPoint = startPoint;
    startPoint = self.pinPath.currentPoint;
    [self.pinPath addCurveToPoint:nextPoint
                    controlPoint1:CGPointMake(startPoint.x, startPoint.y - (startPoint.y - nextPoint.y) / 2.0)
                    controlPoint2:CGPointMake(nextPoint.x, nextPoint.y + (startPoint.y - nextPoint.y) / 2.0)];
    [[UIColor blackColor] setStroke];
    [[UIColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.8] setFill];
    self.pinPath.lineWidth = self.lineWidth;
    [self.pinPath fill];
    [self.pinPath stroke];
    [self.pinPath closePath];

    // bubble around label

    if ([self.annotation.title length] > 0) {
        self.textBubblePath = [UIBezierPath bezierPath];
        CGRect bubbleRect = CGRectInset(CGRectMake(0, self.pinHeight, self.textBubbleSize.width, self.textBubbleSize.height), self.lineWidth / 2, self.lineWidth / 2);
        self.textBubblePath = [UIBezierPath bezierPathWithRoundedRect:bubbleRect
                                                         cornerRadius:bubbleRect.size.height / 2];
        self.textBubblePath.lineWidth = self.lineWidth;
        [self.textBubblePath fill];
        [self.textBubblePath stroke];
    } else {
        self.textBubblePath = nil;
    }

    // center white dot

    self.pinPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius / 3.0 startAngle:0 endAngle:M_PI * 2.0 clockwise:TRUE];
    self.pinPath.lineWidth = self.lineWidth;
    [[UIColor whiteColor] setFill];
    [self.pinPath fill];
}

- (UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event {
    if ([self.pinPath containsPoint:point] || [self.textBubblePath containsPoint:point])
        return self;

    return nil;
}

@end

That yields something like:

enter image description here

Clearly, you can customize this to your heart's content, but it illustrates the basic idea: Write a MKAnnotationView subclass that overrides initWithAnnotation:reuseIdentifier: and implement your own drawRect.

Quinonoid answered 23/5, 2015 at 17:36 Comment(4)
Cant we return just a custom view in viewforannotation delegate method?where the custom view will contain two image viewAuria
Yes, you can also create a custom image and use that for the annotation view's image. In fact, if the annotation views are the same for different annotations, that's preferable. But in my example, each is different, so a custom drawRect makes sense. BTW, even if you use the custom image, rather than drawRect, you can do that in either a MKAnnotationView subclass (in lieu of the custom drawRect) or in viewForAnnotation. IMHO, this sort of complicated rendering is ill-suited to be inside viewForAnnotation, or even in the map view delegate, for that matter.Quinonoid
@RajatDeepSingh join me in chat chat.stackoverflow.com/rooms/79037/…Quinonoid
For people having problems drawing custom annotation under iOS 11, just be sure to set a size for the annotation view, like in this excellent sample.Diagnose

© 2022 - 2024 — McMap. All rights reserved.