I am having some trouble getting a custom annotation to load inside of my map view when I try to place a pin.
import UIKit
import MapKit
import CoreLocation
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate{
@IBAction func ReportBtn(sender: AnyObject) {
//MARK: Report Date And Time Details
let ReportTime = NSDate()
let TimeStamp = NSDateFormatter()
TimeStamp.timeStyle = NSDateFormatterStyle.ShortStyle
TimeStamp.dateStyle = NSDateFormatterStyle.ShortStyle
TimeStamp.stringFromDate(ReportTime)
//MARK: Default Point Annotation Begins
let ReportAnnotation = MKPointAnnotation()
ReportAnnotation.title = "Annotation Created"
ReportAnnotation.subtitle = ReportTime.description
ReportAnnotation.coordinate = locationManager.location!.coordinate
mapView(MainMap, viewForAnnotation: ReportAnnotation)
MainMap.addAnnotation(ReportAnnotation)
}
@IBOutlet weak var MainMap: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.startUpdatingLocation()
self.MainMap.showsUserLocation = true
}
//MARK: - Location Delegate Methods
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02 ))
self.MainMap.setRegion(region, animated: true)
//self.locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError){
print(error.localizedDescription)
}
//MARK:Custom Annotation Begins Here
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
guard !annotation.isKindOfClass(MKUserLocation) else {
return nil
}
/*if annotation.isKindOfClass(MKUserLocation){
//emty return, guard wasn't cooperating
}else{
return nil
}*/
let annotationIdentifier = "AnnotationIdentifier"
var annotationView: MKAnnotationView?
if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(annotationIdentifier){
annotationView = dequeuedAnnotationView
annotationView?.annotation = annotation
}
else{
let av = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
av.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure)
annotationView = av
}
if let annotationView = annotationView {
annotationView.canShowCallout = true
annotationView.image = UIImage(named: "image.png")
}
return annotationView
}
}
I am positive that the button functionality works perfect. With the current code, dumped above, the default red pin annotation appears right where it should. When I tap on the pin, the description I specified also appears without an issue. The only problem I am having with this code is that I cannot get my image to take the place of the boring, default red pin
I recommend subclassing `MKPointAnnotation.
I have included only the necessary code to display a custom map pin. Think of it as a template.
Outline
We will create a point annotation object and assigning a custom image name with the CustomPointAnnotation
class.
We will subclass the MKPointAnnotation
to set image and assign it on the delegate protocol method viewForAnnotation
.
We will add an annotation view to the map after setting the coordinate of the point annotation with a title and a subtitle.
We will implement the viewForAnnotation
method which is an MKMapViewDelegate
protocol method which gets called for pins to display on the map. viewForAnnotation
protocol method is the best place to customise the pin view and assign a custom image to it.
We will dequeue and return a reusable annotation for the given identifier and cast the annotation to our custom CustomPointAnnotation
class in order to access the image name of the pin.
We will create a new image set in Assets.xcassets
and place [email protected] and [email protected] accordingly.
Don't forget plist.
NSLocationAlwaysUsageDescription
and NSLocationWhenInUseUsageDescription
As always test on a real device.
The swizzle 🌀
//1
CustomPointAnnotation.swift
import UIKit
import MapKit
class CustomPointAnnotation: MKPointAnnotation {
var pinCustomImageName:String!
}
//2
ViewController.swift
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
@IBOutlet weak var pokemonMap: MKMapView!
let locationManager = CLLocationManager()
var pointAnnotation:CustomPointAnnotation!
var pinAnnotationView:MKPinAnnotationView!
override func viewDidLoad() {
super.viewDidLoad()
//Mark: - Authorization
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
pokemonMap.delegate = self
pokemonMap.mapType = MKMapType.Standard
pokemonMap.showsUserLocation = true
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = CLLocationCoordinate2D(latitude: 35.689949, longitude: 139.697576)
let center = location
let region = MKCoordinateRegionMake(center, MKCoordinateSpan(latitudeDelta: 0.025, longitudeDelta: 0.025))
pokemonMap.setRegion(region, animated: true)
pointAnnotation = CustomPointAnnotation()
pointAnnotation.pinCustomImageName = "Pokemon Pin"
pointAnnotation.coordinate = location
pointAnnotation.title = "POKéSTOP"
pointAnnotation.subtitle = "Pick up some Poké Balls"
pinAnnotationView = MKPinAnnotationView(annotation: pointAnnotation, reuseIdentifier: "pin")
pokemonMap.addAnnotation(pinAnnotationView.annotation!)
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print(error.localizedDescription)
}
//MARK: - Custom Annotation
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let reuseIdentifier = "pin"
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseIdentifier)
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
annotationView?.canShowCallout = true
} else {
annotationView?.annotation = annotation
}
let customPointAnnotation = annotation as! CustomPointAnnotation
annotationView?.image = UIImage(named: customPointAnnotation.pinCustomImageName)
return annotationView
}
}
There are a few issues you need to deal with.
Firstly, it is necessary to understand how MKMapView
displays an annotation view from an annotation. There are
An MKAnnotation
holds the data for a location on the map. You create this data and hand it to MKMapView
. At some point in the future, when the map view is ready to display the annotation it will call back to the delegate and ask it to create an MKAnnotationView
for an MKAnnotation
. The delegate creates and returns the view and the map view displays it. You specify the delegate in the storyboard, or in code e.g. mapView.delegate = self
.
Tracking the users location is complicated by:
Your code needs to deal with authorisation by checking CLLocationManager.authorizationStatus
, and implementing CLLocationManagerDelegate
methods.
Note that to use requestWhenInUseAuthorization
requires entry for NSLocationWhenInUseUsageDescription
in Info.plist
Example project on GitHub.
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
// Instance of location manager.
// This is is created in viewDidLoad() if location services are available.
var locationManager: CLLocationManager?
// Last location made available CoreLocation.
var currentLocation: MKUserLocation? {
didSet {
// Hide the button if no location is available.
button.hidden = (currentLocation == nil)
}
}
// Date formatter for formatting dates in annotations.
// We use a lazy instance so that it is only created when needed.
lazy var formatter: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.timeStyle = NSDateFormatterStyle.ShortStyle
formatter.dateStyle = NSDateFormatterStyle.ShortStyle
return formatter
}()
@IBOutlet var button: UIButton!
@IBOutlet var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
// Track the user's location if location services are enabled.
if CLLocationManager.locationServicesEnabled() {
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
switch CLLocationManager.authorizationStatus() {
case .AuthorizedAlways, .AuthorizedWhenInUse:
// Location services authorised.
// Start tracking the user.
locationManager?.startUpdatingLocation()
mapView.showsUserLocation = true
default:
// Request access for location services.
// This will call didChangeAuthorizationStatus on completion.
locationManager?.requestWhenInUseAuthorization()
}
}
}
//
// CLLocationManagerDelegate method
// Called by CLLocationManager when access to authorisation changes.
//
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .AuthorizedAlways, .AuthorizedWhenInUse:
// Location services are authorised, track the user.
locationManager?.startUpdatingLocation()
mapView.showsUserLocation = true
case .Denied, .Restricted:
// Location services not authorised, stop tracking the user.
locationManager?.stopUpdatingLocation()
mapView.showsUserLocation = false
currentLocation = nil
default:
// Location services pending authorisation.
// Alert requesting access is visible at this point.
currentLocation = nil
}
}
//
// MKMapViewDelegate method
// Called when MKMapView updates the user's location.
//
func mapView(mapView: MKMapView, didUpdateUserLocation userLocation: MKUserLocation) {
currentLocation = userLocation
}
@IBAction func addButtonTapped(sender: AnyObject) {
guard let coordinate = currentLocation?.coordinate else {
return
}
let reportTime = NSDate()
let formattedTime = formatter.stringFromDate(reportTime)
let annotation = MKPointAnnotation()
annotation.title = "Annotation Created"
annotation.subtitle = formattedTime
annotation.coordinate = coordinate
mapView.addAnnotation(annotation)
}
//
// From Bhoomi's answer.
//
// MKMapViewDelegate method
// Called when the map view needs to display the annotation.
// E.g. If you drag the map so that the annotation goes offscreen, the annotation view will be recycled. When you drag the annotation back on screen this method will be called again to recreate the view for the annotation.
//
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
guard !annotation.isKindOfClass(MKUserLocation) else {
return nil
}
let annotationIdentifier = "AnnotationIdentifier"
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(annotationIdentifier)
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
annotationView!.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure)
annotationView!.canShowCallout = true
}
else {
annotationView!.annotation = annotation
}
annotationView!.image = UIImage(named: "smile")
return annotationView
}
}
check your image.png in your project bundle or Assets.xcassets
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !annotation.isKind(of: MKUserLocation.self) else {
return nil
}
let annotationIdentifier = "AnnotationIdentifier"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier)
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
annotationView?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
annotationView!.canShowCallout = true
}
else {
annotationView!.annotation = annotation
}
annotationView!.image = UIImage(named: "image.png")
return annotationView
}
Do as follow may be work for you.
1) Create custom class for the Annotation Pin.
class CustomPointAnnotation: MKPointAnnotation {
var imageName: UIImage!
}
2)Define variable as below.
var locationManager = CLLocationManager()
3) Call below method in viewDidLoad()
func checkLocationAuthorizationStatus() {
if CLLocationManager.authorizationStatus() == .AuthorizedAlways {
map.showsUserLocation = false
} else {
locationManager.requestWhenInUseAuthorization()
}
}
4) Put below code in viewWillAppear()
self.map.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.delegate = self
dispatch_async(dispatch_get_main_queue(),{
self.locationManager.startUpdatingLocation()
})
5) Most important implement below method.
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if !(annotation is CustomPointAnnotation) {
return nil
}
let reuseId = "Location"
var anView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if anView == nil {
anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
anView!.canShowCallout = true
}
else {
anView!.annotation = annotation
}
let cpa = annotation as! CustomPointAnnotation
anView!.image = cpa.imageName
return anView
}
6) Execute below code where you you have received custom pin image
let strLat = "YOUR LATITUDE"
let strLon = "YOUR LONGITUDE"
let info = CustomPointAnnotation()
info.coordinate = CLLocationCoordinate2DMake(strLat.toDouble()!,strLon.toDouble()!)
info.imageName = resizedImage
info.title = dict!["locationName"]! as? String
self.map.addAnnotation(info)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With