Swift Different Images For Annotation

Swift different images for Annotation

In the viewForAnnotation delegate method, set the image based on which annotation the method is being called for.

Be sure to do this after the view is dequeued or created (and not only in the if anView == nil part). Otherwise, annotations that use a dequeued view will show the image of the annotation that used the view previously.

With the basic MKPointAnnotation, one crude way to tell annotations apart is by their title but that's not very flexible.

A better approach is to use a custom annotation class that implements the MKAnnotation protocol (an easy way to do that is to subclass MKPointAnnotation) and add whatever properties are needed to help implement the custom logic.

In the custom class, add a property, say imageName, which you can use to customize the image based on the annotation.

This example subclasses MKPointAnnotation:

class CustomPointAnnotation: MKPointAnnotation {
var imageName: String!
}

Create annotations of type CustomPointAnnotation and set their imageName:

var info1 = CustomPointAnnotation()
info1.coordinate = CLLocationCoordinate2DMake(42, -84)
info1.title = "Info1"
info1.subtitle = "Subtitle"
info1.imageName = "1.png"

var info2 = CustomPointAnnotation()
info2.coordinate = CLLocationCoordinate2DMake(32, -95)
info2.title = "Info2"
info2.subtitle = "Subtitle"
info2.imageName = "2.png"

In viewForAnnotation, use the imageName property to set the view's image:

func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if !(annotation is CustomPointAnnotation) {
return nil
}

let reuseId = "test"

var anView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if anView == nil {
anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
anView.canShowCallout = true
}
else {
anView.annotation = annotation
}

//Set annotation-specific properties **AFTER**
//the view is dequeued or created...

let cpa = annotation as CustomPointAnnotation
anView.image = UIImage(named:cpa.imageName)

return anView
}

How to set DIFFERENT image pins in different locations SWIFT

You can add an image property to your AnnotationPin class and then in viewForAnnotation you use a conditional downcast to see if you are dealing with one of your annotations. If you are then you can use the image property

class AnnotationPin:  NSObject, MKAnnotation {
let coordinate: CLLocationCoordinate2D
let title: String?
let subtitle: String?
let image: UIImage?

init(title:String, subtitle: String, image: UIImage, coordinate: CLLocationCoordinate2D) {
self.title = title
self.subtitle = subtitle
self.coordinate = coordinate
self.image = image
super.init()
}}


func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

guard let myAnnotation = annotation as? AnnotationPin else {
return nil
}

let annotationIdentifier = "AnnotationIdentifier"

var annotationView: MKAnnotationView?
if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier) {
annotationView = dequeuedAnnotationView
annotationView?.annotation = annotation
}
else {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
annotationView?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
}

if let annotationView = annotationView {

annotationView.canShowCallout = true
annotationView.image = myAnnotation.image
}

return annotationView
}

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
}
}

Can I assign different images to every pin in the map?

First, let me say that I think that matt has nailed the root of the issue, namely that if you have annotations with their own images, you should define your own annotation type that captures the URL of the image, e.g.

class CustomAnnotation: NSObject, MKAnnotation {
dynamic var coordinate: CLLocationCoordinate2D
dynamic var title: String?
dynamic var subtitle: String?

var imageURL: URL?

init(coordinate: CLLocationCoordinate2D, title: String? = nil, subtitle: String? = nil, imageURL: URL? = nil) {
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
self.imageURL = imageURL

super.init()
}
}

When you add your annotations, make sure to supply the URL, and you're off to the races. The only additional thing I'd point out is that you really want to keep track of which network request is associated with which annotation (so that you can cancel it if you need). So I would add a URLSessionTask property to the annotation view class:

class CustomAnnotationView: MKAnnotationView {
weak var task: URLSessionTask? // keep track of this in case we need to cancel it when the annotation view is re-used
}

Frankly, I’d pull all of your complicated configuration code out of the mapView(_:viewFor:) method and put it in the annotation view classes, for a better division of labor and avoiding view controller bloat.

So, for example, a custom annotation view for the MKUserLocation annotation:

class CustomUserAnnotationView: MKAnnotationView {
static let reuseIdentifier = Bundle.main.bundleIdentifier! + ".customUserAnnotationView"
private let size = CGSize(width: 17, height: 17)

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

image = UIImage(named: "myLocation.png")?.resized(to: size)
}

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

And another for your CustomAnnotation annotation with imageURL property, where it will asynchronously fetch the desired image:

class CustomAnnotationView: MKAnnotationView {
static let reuseIdentifier = Bundle.main.bundleIdentifier! + ".customAnnotationView"

private weak var task: URLSessionTask?
private let size = CGSize(width: 17, height: 17)

override var annotation: MKAnnotation? {
didSet {
if annotation === oldValue { return }

task?.cancel()
image = UIImage(named: "emptyPhoto.png")?.resized(to: size)
updateImage(for: annotation)
}
}

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

canShowCallout = true
rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
image = UIImage(named: "emptyPhoto.png")?.resized(to: size)
}

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

private func updateImage(for annotation: MKAnnotation?) {
guard let annotation = annotation as? CustomAnnotation, let url = annotation.imageURL else { return }

let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data,
let image = UIImage(data: data)?.resized(to: self.size),
error == nil else {
print(error ?? "Unknown error")
return
}

DispatchQueue.main.async {
UIView.transition(with: self, duration: 0.2, options: .transitionCrossDissolve, animations: {
self.image = image
}, completion: nil)
}
}
task.resume()
self.task = task
}
}

Then, in iOS 11 and later, my view controller can simply register these two classes in viewDidLoad:

mapView.register(CustomUserAnnotationView.self, forAnnotationViewWithReuseIdentifier: CustomUserAnnotationView.reuseIdentifier)
mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: CustomAnnotationView.reuseIdentifier)

And then, the mapView(_:viewFor:) distills down to a much simpler method:

extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier: String

switch annotation {
case is MKUserLocation: identifier = CustomUserAnnotationView.reuseIdentifier
case is CustomAnnotation: identifier = CustomAnnotationView.reuseIdentifier
default: return nil
}

return mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation)
}
}

Note, I've tried to fix a whole bunch of other issues buried in your viewForAnnotation method, notably:

  1. Your image resizing logic (a) was repeated a couple of times; and (b) didn't call UIGraphicsEndImageContext. So, I'd suggest pulling that out (for example in this UIImage extension) and might as well use UIGraphicsImageRenderer to simplify it:

    extension UIImage {
    func resized(to size: CGSize) -> UIImage {
    return UIGraphicsImageRenderer(size: size).image { _ in
    draw(in: CGRect(origin: .zero, size: size))
    }
    }
    }

    You might want to consider whether you want aspect fill or something like that, but here are a few other permutations of the idea: https://stackoverflow.com/a/28513086/1271826. Or, perhaps better, take a look at the resizing routines of AlamofireImage or Kingfisher.

    But the take home message is that you should pull the gory resizing logic out of viewForAnnotation and into its own routine/library.

  2. You really should employ dequeue logic for the user location, too.

  3. Note, I'm just doing simple URLSession.shared.dataTask without looking for caches, etc. You obviously can get more sophisticated here (e.g. caching the resized image views ,etc.).

  4. It's not a big deal, but I find this construct to be a bit unwieldy:

    guard !annotation.isKind(of: MKUserLocation.self) else { ... }

    So I simplified that to use is test. E.g.:

    if annotation is MKUserLocation { ... }

    It's largely the same thing, but a bit more intuitive.

  5. Note, the above routine, like yours, uses a resized placeholder image for the annotation view's image property. For the sake of future readers, let me say that this is important, because the standard annotation view doesn't gracefully handle asynchronous changes in size of the image. You can either modify that class to do so, or, easier, like we have here, use a standard sized placeholder image.

Note, see the prior revision of this answer for iOS 10.x and earlier.

MapKit custom image for multiple annotations

I have solved this by creating a news object and give it the value of annotation as News, then use news.getCategoryId()

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

let news = annotation as! News

var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "newsAnnotationView")

if annotationView == nil {
annotationView = MKAnnotationView(annotation: news, reuseIdentifier: "newsAnnotationView")
}

switch(news.getCategoryId()) {
case 1: // Other
annotationView?.image = UIImage(named: "ic_other")
break;
case 2: // sports
annotationView?.image = UIImage(named: "ic_sports")
break;
case 3: // education
annotationView?.image = UIImage(named: "ic_education")
break;
case 4: // crime
annotationView?.image = UIImage(named: "ic_crime")
break;
case 5: // health
annotationView?.image = UIImage(named: "ic_health")
break;
default:
break;
}

annotationView?.canShowCallout = true
return annotationView
}


Related Topics



Leave a reply



Submit