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:
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
Get a List of All Contacts on Ios
How to Play a Local Video With Swift
Add Image to Uialertaction in Uialertcontroller
How to Use Safe Area Layout Programmatically
When Should We Use "Embedded Binaries" Rather Than "Linked Frameworks" in Xcode
Detect Permission of Camera in iOS
Enumerate All Keychain Items in My iOS Application
Storyboard Doesn't Contain a View Controller With Identifier
Do I Need to Disable Nslog Before Release Application
Programmatically Create a Uiview With Color Gradient
How to Determine the Os Version At Runtime in Os X or iOS (Without Using Gestalt)
Make a Phone Call Programmatically
How to Create Radio Buttons and Checkbox in Swift (Ios)