Mkmapview: Instead of Annotation Pin, a Custom View

MKMapView: Instead of Annotation Pin, a custom view

When you want to use your own image for an annotation view, you should create an MKAnnotationView instead of an MKPinAnnotationView.

MKPinAnnotationView is a subclass of MKAnnotationView so it has an image property but it generally overrides that and draws a pin image (that's what it's for).

So change the code to:

-(MKAnnotationView *)mapView:(MKMapView *)mV viewForAnnotation:(id )annotation 
{
MKAnnotationView *pinView = nil;
if(annotation != mapView.userLocation)
{
static NSString *defaultPinID = @"com.invasivecode.pin";
pinView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
if ( pinView == nil )
pinView = [[MKAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:defaultPinID];

//pinView.pinColor = MKPinAnnotationColorGreen;
pinView.canShowCallout = YES;
//pinView.animatesDrop = YES;
pinView.image = [UIImage imageNamed:@"pinks.jpg"]; //as suggested by Squatch
}
else {
[mapView.userLocation setTitle:@"I am here"];
}
return pinView;
}



Notice that animatesDrop is also commented out since that property only exists in MKPinAnnotationView.

If you still want your image annotations to drop, you'll have to do the animation yourself. You can search Stack Overflow for "animatesdrop mkannotationview" and you'll find several answers. Here are the first two:

  • Is it possible to call animatesDrop in a MKAnnotationView rather than MKPinAnnotationView?
  • How can I create a custom "pin-drop" animation using MKAnnotationView?

Custom image as annotation pin with two different colour images

You need to use MKAnnotationView instead of MKPinAnnotationView to add a custom image for your pin annotations.

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}

if // Image pin // {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "image")
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "image")
annotationView?.canShowCallout = true
annotationView?.image = UIImage(named: "BLog.png")

let rightButton: AnyObject! = UIButton(type: UIButtonType.detailDisclosure)
annotationView?.rightCalloutAccessoryView = rightButton as? UIView
}
else {
annotationView?.annotation = annotation
}
return annotationView
} else {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "myAnnotation") as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "myAnnotation")
annotationView?.canShowCallout = true
} else {
annotationView?.annotation = annotation
}
if let annotation = annotation as? MyPointAnnotation {
annotationView?.pinTintColor = annotation.pinTintColor
}
return annotationView
}
}

MapKit not showing custom Annotation pin image on iOS9

Instead of creating an MKPinAnnotationView, create a plain MKAnnotationView.

The MKPinAnnotationView subclass tends to ignore the image property since it's designed to show the standard red, green, purple pins only (via the pinColor property).

When you switch to MKAnnotationView, you'll have to comment out the animatesDrop line as well since that property is specific to MKPinAnnotationView.

MKPinAnnotationView custom Image is replaced by pin with animating drop

When you want to use your own image for an annotation view, it's best to create a regular MKAnnotationView instead of an MKPinAnnotationView.

Since MKPinAnnotationView is a subclass of MKAnnotationView, it has an image property but it generally (usually when you don't expect it) ignores it and displays its built-in pin image instead.

So rather than fighting with it, it's best to just use a plain MKAnnotationView.

The big thing you lose by not using MKPinAnnotationView is the built-in drop animation which you'll have to implement manually.

See this previous answer for more details:

MKMapView: Instead of Annotation Pin, a custom view

Custom View for MKPointAnnotation

The basic idea is that you subclass MKAnnotationView and set its image.

You can implement the button below the annotation view any way you want, but make sure to implement a hitTest(_:with:) so that taps are correctly recognized.

E.g.

class CustomAnnotationView: MKAnnotationView {

static let lineWidth: CGFloat = 2
static let pinSize = CGSize(width: 30, height: 37)
static let pinImage: UIImage = image(with: .blue)

private let button: UIButton = DisclosureIndicatorButton()

override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)

image = Self.pinImage
centerOffset = CGPoint(x: 0, y: -Self.pinSize.height / 2)

configure(for: annotation)
configureButton()
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override var annotation: MKAnnotation? {
didSet {
configure(for: annotation)
}
}

// this is required for a tap on the callout/button below the annotation view to be recognized

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let hitView = super.hitTest(point, with: event) else {
let pointInButton = convert(point, to: button)
return button.hitTest(pointInButton, with: event)
}

return hitView
}
}

private extension CustomAnnotationView {
func configure(for annotation: MKAnnotation?) {
canShowCallout = false
button.setTitle(annotation?.title ?? "Unknown", for: .normal)

// if you were also doing clustering, you do that configuration here ...
}

func configureButton() {
addSubview(button)

NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: centerXAnchor),
button.topAnchor.constraint(equalTo: bottomAnchor, constant: 20)
])

button.addTarget(self, action: #selector(didTapButton(_:)), for: .touchUpInside)
}

/// Function to create pin image of the desired color
///
/// You could obviously just create an pre-rendered image in your asset collection,
/// but I find it just as easy to render them programmatically.

static func image(with color: UIColor) -> UIImage {
UIGraphicsImageRenderer(size: pinSize).image { _ in
let bounds = CGRect(origin: .zero, size: pinSize)
let rect = bounds.insetBy(dx: lineWidth / 2, dy: lineWidth / 2)
let r = rect.width / 2
let h = rect.height - r
let theta = acos(r / h)
let center = CGPoint(x: rect.midX, y: rect.midX)
let path = UIBezierPath(arcCenter: center, radius: r, startAngle: .pi / 2 - theta, endAngle: .pi / 2 + theta, clockwise: false)
path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
path.lineWidth = lineWidth
path.close()

color.setFill()
path.fill()
UIColor.white.setStroke()
path.stroke()

let path2 = UIBezierPath(arcCenter: center, radius: r / 3, startAngle: 0, endAngle: .pi * 2, clockwise: true)
UIColor.white.setFill()
path2.fill()
}
}

/// Button handler
///
/// Note, taps on the button are passed to map view delegate via
/// mapView(_:annotationView:calloutAccessoryControlTapped)`.
///
/// Obviously, you could use your own delegate protocol if you wanted.

@objc func didTapButton(_ sender: Any) {
if let mapView = mapView, let delegate = mapView.delegate {
delegate.mapView?(mapView, annotationView: self, calloutAccessoryControlTapped: button)
}
}

/// Map view
///
/// Navigate up view hierarchy until we find `MKMapView`.

var mapView: MKMapView? {
var view = superview
while view != nil {
if let mapView = view as? MKMapView { return mapView }
view = view?.superview
}
return nil
}
}

That results in:

Sample Image

The creation of the button below the annotation view is beyond the scope of the question and could be created like one of the answers discussed in How do I put the image on the right side of the text in a UIButton? In the above example, I’ve just wrapped one of the those solutions in its own class, DisclosureIndicatorButton, but I don’t really want to wade into the debate about the preferred approach, so I’ll let you pick whichever you want.



Related Topics



Leave a reply



Submit