Swift MapKit - Annotations dont show in the MapView - ios

I am trying to create a MapView using MapKit framework which looks for the current user location and displays restaurants nearby. However, when I am trying to show annotations then they don't show up.
I have tried printing (response.mapItems) to check if it works and the result is good because it prints information about some restaurants that were found in the console.
Therefore, I don't know why these are not annotated on the map.
Here is the code that I've made:
import UIKit
import MapKit
import CoreLocation
class RestaurantViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var map: MKMapView!
let manager = CLLocationManager()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
let span:MKCoordinateSpan = MKCoordinateSpanMake(0.025, 0.025)
let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, span)
map.setRegion(region, animated: true)
self.map.showsUserLocation = true
}
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = "restaurant"
request.region = map.region
let search = MKLocalSearch(request: request)
search.start { (response, error) in
guard let response = response else {
return
}
for item in response.mapItems {
print(response.mapItems) - Console shows some restaunrant outputs that were correctly fetched
let annotation = MKPointAnnotation()
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name
DispatchQueue.main.async {
self.map.addAnnotation(annotation)
}
}
}
}
}

You annotation implementation is a little off, allow me to explain how it should be.
Adding annotations to your map relies on two things.
A class conforming to MKAnnotation.
A subclass of MKAnnotationView.
Your map has annotations added to it but it does not know how to show them, so it shows nothing. You can tell it how to show them by implementing viewForAnimation. Take a look at my example below:
class PinAnnotation: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
init(coordinate: CLLocationCoordinate2D) {
self.coordinate = coordinate
super.init()
}
}
class PinAnnotationView: MKAnnotationView {
#available(*, unavailable)
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
#available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init(annotation: PinAnnotation) {
super.init(annotation: annotation, reuseIdentifier: nil)
self.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
self.image = UIImage(named: "mypinimage") // or whatever.
}
}
Here we have the definition for the two objects I spoke of above.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let imageAnnotation = annotation as? ImageAnnotation {
let view = ImageAnnotationView(annotation: imageAnnotation)
return view
}
return nil
}
Here we have the implementation of viewForAnnotation that I spoke of above.

Implement viewForAnnotation
func mapView(_ mapView: MKMapView,viewFor annotation: MKAnnotation) -> MKAnnotationView?
see my demo here customPinAnnotationButton

Related

MKLocalSearch (search.start)always goes in the error flow. (SWIFT)

I've been trying to learn swift for the past few days and just started my own project! As part of the project I wanted to show nearby cocktail bars on a map, I was able to find some nice info online and have been able to show a map with my current location. I also found info on how to find nearby locations: https://developer.apple.com/documentation/mapkit/mklocalsearch/request
Unfortunately this last one never seems to work, it just goes out of the function and does not return any locations, could anyone help me further? Below is the code of my viewcontroller with the function getNearbyLandmarks which doesn't work as intended.
class MapViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet var mapView: MKMapView!
let manager = CLLocationManager()
private var landmarks: [Landmark] = [Landmark]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
manager.stopUpdatingLocation()
render(location)
}
}
func render(_ location: CLLocation){
let coordinate = CLLocationCoordinate2D(latitude: location.coordinate.latitude,
longitude: location.coordinate.longitude)
let span = MKCoordinateSpan(latitudeDelta: 0.01,
longitudeDelta: 0.01)
let region = MKCoordinateRegion(center: coordinate,
span: span)
mapView.delegate = self
mapView.setRegion(region,
animated: true)
let pin = MKPointAnnotation()
pin.coordinate = coordinate
pin.title = "Current location"
mapView.addAnnotation(pin)
self.getNearByLandmarks()
updateAnnotations(from: mapView)
}
func mapView(_ mapViewIcon: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var annotationView = mapViewIcon.dequeueReusableAnnotationView(withIdentifier: "custom")
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation,
reuseIdentifier: "custom")
annotationView?.canShowCallout = true
}else{
annotationView?.annotation = annotation
}
annotationView?.image = UIImage(named: "User")
return annotationView
}
private func getNearByLandmarks(){
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = "coffee"
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start{(response, error) in
if let response = response {
let mapItems = response.mapItems
self.landmarks = mapItems.map{
Landmark(placemark: $0.placemark)
}
}
}
}
private func updateAnnotations(from mapView: MKMapView){
let annotations = self.landmarks.map(LandmarkAnnotation.init)
mapView.addAnnotations(annotations)
}
}
You call getNearByLandmarks, and then immediately try to update the annotations:
self.getNearByLandmarks()
updateAnnotations(from: mapView)
but getNearByLandmarks is asynchronous, and takes some time to complete. You need to update the annotations whenever private var landmarks changes, one way is to update it in all the places that set that, like where you say
self.landmarks = mapItems.map{
Landmark(placemark: $0.placemark)
}
note you don't call updateAnnotations(from: mapView) there.
Or you could add a didSet property observer to landmarks itself so that updateAnnotations is called whenever it changes.

How to change MapKit's pin color in Swift 3/4?

So, I am creating an app with MapKit. So, I need some help of changing the maps pin color from red to any color possible. I tried every way, I can't just not find a solution. Can any one check my code, and help me apply it to my code below of changing the map's pin tintColor. Thanks in advance.
Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
locationManager.requestWhenInUseAuthorization()
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if locations.count > 0 {
let location = locations.last!
print("Accuracy: \(location.horizontalAccuracy)")
if location.horizontalAccuracy < 100 {
manager.stopUpdatingLocation()
let span = MKCoordinateSpan(latitudeDelta: 0.014, longitudeDelta: 0.014)
let region = MKCoordinateRegion(center: location.coordinate, span: span)
mapView.region = region
let location = CLLocation(latitude: latitude, longitude: longitude)
let place = Place(location: location, reference: reference, name: name, address: address)
self.places.append(place)
let annotation = MyHome(location: place.location!.coordinate, title: place.placeName)
DispatchQueue.main.async {
self.mapView.addAnnotation(annotation)
}
}
}
}
}
}
}
}
}
MyHome CLass
import Foundation
import MapKit
class MyHome: NSObject, MKAnnotation {
let coordinate: CLLocationCoordinate2D
let title: String?
init(location: CLLocationCoordinate2D, title: String) {
self.coordinate = location
self.title = title
super.init()
}
}
MapView
fileprivate var locationManager = CLLocationManager()
fileprivate var heading: Double = 0
fileprivate var interactionInProgress = false
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
{
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required public init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
open override func viewDidLoad()
{
super.viewDidLoad()
self.mapView.isRotateEnabled = false
if let annotations = self.annotations
{
addAnnotationsOnMap(annotations)
}
locationManager.delegate = self
}
open override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear(animated)
locationManager.startUpdatingHeading()
}
open override func viewDidDisappear(_ animated: Bool)
{
super.viewDidDisappear(animated)
locationManager.stopUpdatingHeading()
}
open func addAnnotations(_ annotations: [ARAnnotation])
{
self.annotations = annotations
if self.isViewLoaded
{
addAnnotationsOnMap(annotations)
}
}
fileprivate func addAnnotationsOnMap(_ annotations: [ARAnnotation])
{
var mapAnnotations: [MKPointAnnotation] = []
for annotation in annotations
{
if let coordinate = annotation.location?.coordinate
{
let mapAnnotation = MKPointAnnotation()
mapAnnotation.coordinate = coordinate
let text = String(format: "%#, AZ: %.0f, VL: %i, %.0fm", annotation.title != nil ? annotation.title! : "", annotation.azimuth, annotation.verticalLevel, annotation.distanceFromUser)
mapAnnotation.title = text
mapAnnotations.append(mapAnnotation)
}
}
mapView.addAnnotations(mapAnnotations)
mapView.showAnnotations(mapAnnotations, animated: false)
}
open func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading)
{
heading = newHeading.trueHeading
if(!self.interactionInProgress && CLLocationCoordinate2DIsValid(mapView.centerCoordinate))
{
let camera = mapView.camera.copy() as! MKMapCamera
camera.heading = CLLocationDirection(heading);
self.mapView.setCamera(camera, animated: false)
}
}
open func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool)
{
self.interactionInProgress = true
}
open func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool)
{
self.interactionInProgress = false
}
}
you have to change its color in MKMapViewDelegate delegate method
#IBOutlet weak var customMap: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
let anoot = MKPointAnnotation()
anoot.coordinate = CLLocationCoordinate2D.init(latitude: lat, longitude: lng)
customMap.addAnnotation(anoot)
customMap.delegate = self
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
annotationView.pinTintColor = UIColor.green
return annotationView
}
you can do it in your code like this
class ViewController: UIViewController {
var locationManager = CLLocationManager()
#IBOutlet weak var yourMap: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
yourMap.delegate = self
let anoot = MKPointAnnotation()
anoot.coordinate = CLLocationCoordinate2D.init(latitude: 23.0225, longitude: 72.5714)
yourMap.addAnnotation(anoot)
yourMap.delegate = self
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
locationManager.requestWhenInUseAuthorization()
}
}
extension ViewController: CLLocationManagerDelegate, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
annotationView.pinTintColor = UIColor.green
return annotationView
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if locations.count > 0 {
let location = locations.last!
print("Accuracy: \(location.horizontalAccuracy)")
if location.horizontalAccuracy < 100 {
manager.stopUpdatingLocation()
let span = MKCoordinateSpan(latitudeDelta: 0.014, longitudeDelta: 0.014)
let region = MKCoordinateRegion(center: location.coordinate, span: span)
yourMap.region = region
// let location = CLLocation(latitude: latitude, longitude: longitude)
// let place = Place(location: location, reference: reference, name: name, address: address)
// self.places.append(place)
//
// let annotation = MyHome(location: place.location!.coordinate, title: place.placeName)
//
// DispatchQueue.main.async {
//
// self.mapView.addAnnotation(annotation)
// }
}
}
}
}

Swift 3 - Understanding MKAnnotationView and mapView() function

There are some serious inconsistencies in answers due to Swift updating it's API spec constantly and not being graceful in it's deprecations... So I've decided to ask this question here.
My current code accounts for updating annotations through an asynchronous function that downloads an image off the internet as a remote URL. It can fetch the UIImage the data datatype no problem. However, I don't quite understand what is happening or the reasoning as to why we need to dequeue and engineer the solution this way.
The question is, why do we need to design our annotation this way, when we can just cast an annotation view to an annotation and not worry about every update?
Please review the following code and enlighten me:
import Foundation
import UIKit
import MapKit
import CoreLocation
class MapView: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
var locationManager:CLLocationManager!
var firstCenter = false
var regionRadius: CLLocationDistance = 1000
var mapView: MKMapView!
var av:MKPinAnnotationView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
mapView = MKMapView(
frame:
CGRect(
x: 0,
y: 0,
width: view.frame.width,
height: view.frame.height
)
)
mapView.mapType = MKMapType.standard
mapView.isZoomEnabled = true
mapView.isScrollEnabled = true
mapView.delegate = self
view.addSubview(mapView)
mapView.center = view.center
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
print("Looking for locations")
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
}
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation = locations[0]
if firstCenter == false {
centerMapOnLocation(location: userLocation)
firstCenter = true
let location2d = CLLocationCoordinate2D(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let url = URL(string: "https://graph.facebook.com/" + FB.id + "/picture?width=50&height=50")
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
let image = UIImage(data: data!)
let a = PinAnnotation()
a.title = "Netflix and Chill"
a.coordinate = location2d
a.image = image
self.mapView.addAnnotation(a)
self.mapView.showAnnotations(self.mapView.annotations, animated: true)
}
}
}
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error \(error)")
}
public func centerMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate,regionRadius * 2.0, regionRadius * 2.0)
mapView.setRegion(coordinateRegion, animated: true)
}
internal func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "pin")
if annotationView != nil {
annotationView?.annotation = annotation
} else {
annotationView = MKAnnotationView(
annotation: annotation,
reuseIdentifier: "pin"
)
}
annotationView?.cornerRadius = 25
let anno = annotation as? PinAnnotation
annotationView?.image = anno?.image
return annotationView
}
}
class PinAnnotation: MKPointAnnotation {
var image:UIImage!
}
Thank you for your time, Chris
The reason they designed it this way was for performance reasons. This is a direct quote from the their official documentation.
"For performance reasons, you should generally reuse MKAnnotationView objects in your map views. As annotation views move offscreen, the map view moves them to an internally managed reuse queue. As new annotations move onscreen, and your code is prompted to provide a corresponding annotation view, you should always attempt to dequeue an existing view before creating a new one. Dequeueing saves time and memory during performance-critical operations such as scrolling."

iOS Swift MapKit Custom Annotation [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
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
}
}
Added Information
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.
Pokémon Pin
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 image#3x.png and image#2x.png 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.
MKMapView and annotations
Firstly, it is necessary to understand how MKMapView displays an annotation view from an annotation. There are
MKMapView - displays the map and manages annotations.
MKMapViewDelegate - you return data from specific functions to MKMapView.
MKAnnotation - contains data about a location on the map.
MKAnnotationView - displays an annotation.
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.
Location
Tracking the users location is complicated by:
Permission is needed from the user before location tracking is enabled.
There is a delay after the user allows tracking, until the user's location is available.
Location services might not even be enabled.
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
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)
From the code and according to the MapKit guide, your code look correct. I am thinking that it could be this line annotationView.image = UIImage(named: "image.png")
Is there a chance that image.png could be the wrong image name or not added in to the project when compile? Also just fyi, if you are using .xcassets, you does not have to add a .png.
As annotationView.image is a optional, when the image UIImage(named: "image.png") is nil, it will not crash but just render the default pin image.
If this is not the issue, please provide more info on the debugging steps that you have taken so the rest of us can understand better and help you. Cheers =)

Cannot change pins for images in a map

I have a map where I mark points based on Parse data. This works correctly. When I try to change the red pins of the map with a custom image, it seems that everything is OK but when I run the app in the simulator, the red pins are there and my image doesn't appear. I have tried some similar versions of the code I have with the same result. I tried to print the annotationView in the console and seems that have the image saved the line before the return. Where is the error? What I have to do to show the custom image correctly? Thanks.
import UIKit
import MapKit
import CoreLocation
import Foundation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
var MapViewLocationManager:CLLocationManager! = CLLocationManager()
var currentLoc: PFGeoPoint! = PFGeoPoint()
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.mapView.showsUserLocation = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
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.5, longitudeDelta: 0.5))
self.mapView.setRegion(region, animated: true)
self.locationManager.stopUpdatingLocation()
}
override func viewDidAppear(animated: Bool) {
let annotationQuery = PFQuery(className: "_User")
currentLoc = PFGeoPoint(location: MapViewLocationManager.location)
annotationQuery.whereKey("restaurant", equalTo: true)
annotationQuery.whereKey("restaurantPosition", nearGeoPoint: currentLoc, withinMiles: 6000)
annotationQuery.findObjectsInBackgroundWithBlock {
(posts, error) -> Void in
if error == nil {
// The find succeeded.
print("Successful query for annotations")
let myPosts = posts as [PFObject]!
for post in myPosts {
let point = post["restaurantPosition"] as! PFGeoPoint
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2DMake(point.latitude, point.longitude)
self.mapViewN(self.mapView, viewForAnnotation: annotation).annotation = annotation
self.mapView.addAnnotation(annotation)
}
} else {
// Log details of the failure
print("Error: \(error)")
}
}
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Error: " + error.localizedDescription)
}
func mapViewN(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView {
let identifier = "pin"
// Reuse the annotation if possible
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier)
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView!.canShowCallout = true
annotationView!.image = UIImage(named: "map_marker_2")
}
else {
annotationView!.annotation = annotation
}
print(annotationView?.image)
return annotationView!
}
}
Your method map view:viewForAnnotation: is misnamed. You have
func mapViewN(mapView: MKMapView,
viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView
That N in the name does not belong there, so your delegate method will never be called.
(The method name for a delegate method must be exactly correct or the method doesn't get called. For a required delegate method you may even crash.)
It should be:
func mapView(mapView: MKMapView,
viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView
mapViewN:viewForAnnotation delegate is called?
Maybe you should rename mapViewN to mapView.
func mapViewN(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView {
->
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {

Resources