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:
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 thisUIImage
extension) and might as well useUIGraphicsImageRenderer
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.You really should employ dequeue logic for the user location, too.
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.).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.
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 theimage
. 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
How to Convert My Device Token (Nsdata) into an Nsstring
Given a View, How to Get Its Viewcontroller
How to Set the Height of Tableheaderview (Uitableview) with Autolayout
Autolayout: Removefromsuperview/Removeconstraints Throws Exception and Crashes Hard
Programmatically Navigate to Another View Controller/Scene
How to Create a Category in Xcode 6 or Higher
Nscamerausagedescription in iOS 10.0 Runtime Crash
How Can a Web Application Send Push Notifications to iOS Devices
How to Create a Centered Uicollectionview Like in Spotify's Player
How to Resize Uiimageview Based on Uiimage's Size/Ratio in Swift 3
Swift Apply .Uppercasestring to Only the First Letter of a String
How to Get an Iso 8601 Date on iOS
Nspersistentcontainer Concurrency for Saving to Core Data
iOS Keeping Old Launch Screen and App Icon After Update
How to Capitalize Each Word in a String Using Swift iOS
No Avplayer Delegate? How to Track When Song Finished Playing? Objective C iPhone Development