I am trying to create a custom MKPointAnnotation to use on a map view. It looks very much like the one used in Apple's Photos:
I will be retrieving a number of photos from a server, along with their location. I then want to display an annotation like the one above, with the image inside the annotation.
I currently have a program that can add a normal MKPointAnnotation at the right coordinate, and can also retrieve the relevant photo from the server.
All I want is to style my MKPointAnnotation to look just like that.
I have tried following other answers but I think this is slightly different because I want to show an image on a template every time.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? from MKMapViewDelegate is the function you need to override.
It allows you to provide "custom" views for the each annotation. Within this function, you can either dequeue a custom view (subclass of MKAnnotationView) and set custom properties OR you can dequeue a regular MKAnnotationView which has a property image.
You can set that property to display custom images. I'd rather use my own annotationView anyway as you can add custom layouts (labels, imageViews, etc..) and themes (colours, layers, etc..).
Example:
//
// ViewController.swift
// Maps
//
// Created by Brandon T on 2017-02-20.
// Copyright © 2017 XIO. All rights reserved.
//
import UIKit
import MapKit
class ImageAnnotation : NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
var image: UIImage?
var colour: UIColor?
override init() {
self.coordinate = CLLocationCoordinate2D()
self.title = nil
self.subtitle = nil
self.image = nil
self.colour = UIColor.white
}
}
class ImageAnnotationView: MKAnnotationView {
private var imageView: UIImageView!
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
self.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
self.imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
self.addSubview(self.imageView)
self.imageView.layer.cornerRadius = 5.0
self.imageView.layer.masksToBounds = true
}
override var image: UIImage? {
get {
return self.imageView.image
}
set {
self.imageView.image = newValue
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ViewController: UIViewController, MKMapViewDelegate {
var mapView: MKMapView!
var locationManager: CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
self.initControls()
self.doLayout()
self.loadAnnotations()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func initControls() {
self.mapView = MKMapView()
self.mapView.isRotateEnabled = true
self.mapView.showsUserLocation = true
self.mapView.delegate = self
let center = CLLocationCoordinate2DMake(43.761539, -79.411079)
let region = MKCoordinateRegionMake(center, MKCoordinateSpanMake(0.005, 0.005))
self.mapView.setRegion(region, animated: true)
}
func doLayout() {
self.view.addSubview(self.mapView)
self.mapView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.mapView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
self.mapView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.mapView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.mapView.translatesAutoresizingMaskIntoConstraints = false
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation.isKind(of: MKUserLocation.self) { //Handle user location annotation..
return nil //Default is to let the system handle it.
}
if !annotation.isKind(of: ImageAnnotation.self) { //Handle non-ImageAnnotations..
var pinAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "DefaultPinView")
if pinAnnotationView == nil {
pinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "DefaultPinView")
}
return pinAnnotationView
}
//Handle ImageAnnotations..
var view: ImageAnnotationView? = mapView.dequeueReusableAnnotationView(withIdentifier: "imageAnnotation") as? ImageAnnotationView
if view == nil {
view = ImageAnnotationView(annotation: annotation, reuseIdentifier: "imageAnnotation")
}
let annotation = annotation as! ImageAnnotation
view?.image = annotation.image
view?.annotation = annotation
return view
}
func loadAnnotations() {
let request = NSMutableURLRequest(url: URL(string: "https://i.imgur.com/zIoAyCx.png")!)
request.httpMethod = "GET"
let session = URLSession(configuration: URLSessionConfiguration.default)
let dataTask = session.dataTask(with: request as URLRequest) { (data, response, error) in
if error == nil {
let annotation = ImageAnnotation()
annotation.coordinate = CLLocationCoordinate2DMake(43.761539, -79.411079)
annotation.image = UIImage(data: data!, scale: UIScreen.main.scale)
annotation.title = "Toronto"
annotation.subtitle = "Yonge & Bloor"
DispatchQueue.main.async {
self.mapView.addAnnotation(annotation)
}
}
}
dataTask.resume()
}
}
If you want set in map many location with different pictures use this code:
#Brandon, Thank you
#objc func loadAnnotations() {
for item in locations{
DispatchQueue.main.async {
let request = NSMutableURLRequest(url: URL(string: "<YourPictureUrl>")!)
request.httpMethod = "GET"
let session = URLSession(configuration: URLSessionConfiguration.default)
let dataTask = session.dataTask(with: request as URLRequest) { (data, response, error) in
if error == nil {
let annotation = ImageAnnotation()
annotation.coordinate = CLLocationCoordinate2DMake(Double(item["pl_lat"] as! String)!,
Double(item["pl_long"] as! String)!)
annotation.image = UIImage(data: data!, scale: UIScreen.main.scale)
annotation.title = "T"
annotation.subtitle = ""
DispatchQueue.main.async {
self.mapView.addAnnotation(annotation)
}
}
}
dataTask.resume()
}
}
}
Related
I'm currently building an Uber clone with Swift and Firebase, following Stephan Dowless' course on Udemy to learn more about MapKit, and so far it's going well, but I'm struggling to add a polyline overlay to the map, showing the route from the user's current location to the annotation (which is added to the mapView by tapping one of the search results displayed in a tableView).
I've looked for other similar questions on SO but haven't found anything that answers my question. I've also tried cloning other projects that use this feature (the completed Uber clone from the Udemy course, this Ray Wenderlich tutorial and this article on polylines using SwiftUI) to check whether it's my code that's the problem but they all present the same problem i.e. the annotations appear on the screen but the overlay doesn't appear at all.
In my app, tapping "Starbucks" on the tableView in this screen produces this screen (showing the annotation for Starbucks and user's current location, but no overlay).
Similarly, running the SwiftUI MapKit tutorial app from Medium mentioned previously shows this (both annotations but no overlay).
This leads me to believe that it's something wrong on my side. I've also tried running these apps on my phone (iPhone 7) with the same issues.
Here are the relevant lines of code:
Declaration of properties including route and mapView
// MARK:- Properties
private let mapView = MKMapView()
private var searchResults = [MKPlacemark]()
private var route: MKRoute?
mapView delegate set to self (function called when user is confirmed to be logged in)
private func configureMapView() {
view.addSubview(mapView)
mapView.frame = view.frame
mapView.delegate = self
mapView.showsUserLocation = true
mapView.userTrackingMode = .follow
}
MapView Functions
// MARK:- MapView Functions
extension HomeViewController: MKMapViewDelegate {
private func generatePolyline(toDestination destination: MKMapItem) {
let request = MKDirections.Request()
request.source = MKMapItem.forCurrentLocation()
request.destination = destination
request.transportType = .automobile
let directionRequest = MKDirections(request: request)
directionRequest.calculate { (response, error) in
guard let response = response else { return }
self.route = response.routes[0]
guard let polyline = self.route?.polyline else { return }
self.mapView.addOverlay(polyline)
}
}
private func searchBy(naturalLanguageQuery: String, completion: #escaping([MKPlacemark]) -> Void) {
var results = [MKPlacemark]()
let request = MKLocalSearch.Request()
request.region = mapView.region
request.naturalLanguageQuery = naturalLanguageQuery
let search = MKLocalSearch(request: request)
search.start { (response, error) in
guard let response = response else { return }
response.mapItems.forEach { (item) in
results.append(item.placemark)
}
completion(results)
}
}
// Change driver annotation appearance to Uber arrow
public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? DriverAnnotation {
let view = MKAnnotationView(annotation: annotation, reuseIdentifier: DriverAnnotation.identifier)
view.image = #imageLiteral(resourceName: "chevron-sign-to-right")
return view
}
return nil
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let route = self.route {
let polyline = route.polyline
let lineRenderer = MKPolylineRenderer(overlay: polyline)
lineRenderer.strokeColor = .mainBlueTint
lineRenderer.lineWidth = 4
return lineRenderer
}
return MKOverlayRenderer()
}
}
TableView didSelectRowAt Method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let placemark = self.searchResults[indexPath.row]
configureActionButtonState(config: .dismissActionView)
let destination = MKMapItem(placemark: placemark)
self.generatePolyline(toDestination: destination)
self.dismissInputView { _ in
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
self.mapView.addAnnotation(annotation)
self.mapView.selectAnnotation(annotation, animated: true)
}
}
Finally, here's the whole view controller, just in case I've missed anything above. Please let me know if any further information is required to answer the question and I'll provide it.
Entire view controller
import UIKit
import Firebase
import MapKit
private enum ActionButtonConfiguration {
case showMenu
case dismissActionView
init() {
self = .showMenu
}
}
class HomeViewController: UIViewController {
// MARK:- Properties
private let mapView = MKMapView()
private let locationManager = LocationHandler.shared.locationManager
private let inputActivationView = LocationInputActivationView()
private let locationInputView = LocationInputView()
private var searchResults = [MKPlacemark]()
private var actionButtonConfig = ActionButtonConfiguration()
private var route: MKRoute?
private let tableView = UITableView()
private var user: User? {
didSet {
locationInputView.user = user
}
}
private let actionButton: UIButton = {
let button = UIButton()
button.setImage(#imageLiteral(resourceName: "baseline_menu_black_36dp").withRenderingMode(.alwaysOriginal), for: .normal)
button.addTarget(self, action: #selector(didTapActionButton), for: .touchUpInside)
return button
}()
// MARK:- Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
checkIfUserLoggedIn()
locationManagerDidChangeAuthorization(locationManager!)
}
// MARK:- Selectors
#objc private func didTapActionButton() {
switch actionButtonConfig {
case .showMenu:
print("Show menu")
case .dismissActionView:
removeAnnotationsAndOverlays()
UIView.animate(withDuration: 0.3) {
self.configureActionButtonState(config: .showMenu)
self.inputActivationView.alpha = 1
}
}
}
// MARK:- API
private func fetchUserData() {
guard let currentUserId = Auth.auth().currentUser?.uid else { return }
Service.shared.fetchUserData(uid: currentUserId) { user in
self.user = user
}
}
// N.B. Service.shared.fetchDrivers automatically gets called every time the location of the driver changes since it is observing the database via geofire (see definition of this within Service.swift)
private func fetchDrivers() {
guard let location = locationManager?.location else { return }
Service.shared.fetchDrivers(location: location) { (driver) in
guard let coordinate = driver.location?.coordinate else { return }
let annotation = DriverAnnotation(uid: driver.uid, coordinate: coordinate)
var driverIsVisible: Bool {
return self.mapView.annotations.contains { annotation -> Bool in
guard let driverAnnotation = annotation as? DriverAnnotation else { return false }
if driverAnnotation.uid == driver.uid {
// Driver is already visible - update driver location whenever this function is called
driverAnnotation.updateAnnotationPosition(withCoordinate: coordinate)
return true
}
// Driver is not visible
return false
}
}
// If driver is not visible then add to map
if !driverIsVisible {
self.mapView.addAnnotation(annotation)
}
}
}
private func checkIfUserLoggedIn() {
if Auth.auth().currentUser == nil {
// User is not logged in
print("DEBUG: User is not logged in")
DispatchQueue.main.async {
let nav = UINavigationController(rootViewController: LoginViewController())
nav.isModalInPresentation = true
nav.modalPresentationStyle = .fullScreen
self.present(nav, animated: true, completion: nil)
}
} else {
// User is logged in
configure()
}
}
private func logOut() {
do {
try Auth.auth().signOut()
DispatchQueue.main.async {
let nav = UINavigationController(rootViewController: LoginViewController())
nav.isModalInPresentation = true
nav.modalPresentationStyle = .fullScreen
self.present(nav, animated: true, completion: nil)
}
} catch {
print("DEBUG: Error signing user out: \(error)")
}
}
// MARK:- Public Helper Functions
public func configure() {
configureUI()
fetchUserData()
fetchDrivers()
}
public func configureUI() {
configureMapView()
configureActionButton()
configureInputActivationView()
configureTableView()
}
// MARK:- Private Helper Functions
private func configureActionButton() {
view.addSubview(actionButton)
actionButton.anchor(top: view.safeAreaLayoutGuide.topAnchor, left: view.safeAreaLayoutGuide.leftAnchor,
paddingTop: 16, paddingLeft: 16, width: 30, height: 30)
}
private func configureActionButtonState(config: ActionButtonConfiguration) {
switch config {
case .showMenu:
self.actionButton.setImage(#imageLiteral(resourceName: "baseline_menu_black_36dp").withRenderingMode(.alwaysOriginal), for: .normal)
self.actionButtonConfig = .showMenu
case .dismissActionView:
self.actionButton.setImage(#imageLiteral(resourceName: "baseline_arrow_back_black_36dp-1").withRenderingMode(.alwaysOriginal), for: .normal)
self.actionButtonConfig = .dismissActionView
}
}
private func configureMapView() {
view.addSubview(mapView)
mapView.frame = view.frame
mapView.delegate = self
mapView.showsUserLocation = true
mapView.userTrackingMode = .follow
}
private func configureInputActivationView() {
inputActivationView.delegate = self
view.addSubview(inputActivationView)
inputActivationView.centerX(inView: view)
inputActivationView.anchor(top: actionButton.bottomAnchor, left: view.safeAreaLayoutGuide.leftAnchor, right: view.safeAreaLayoutGuide.rightAnchor, paddingTop: 18, paddingLeft: 20, paddingRight: 20, height: 40)
// Animate inputActivationView (fade in)
inputActivationView.alpha = 0
UIView.animate(withDuration: 2) {
self.inputActivationView.alpha = 1
}
}
private func configureLocationInputView() {
locationInputView.delegate = self
view.addSubview(locationInputView)
locationInputView.anchor(top: view.topAnchor, left: view.leftAnchor, right: view.rightAnchor, height: 200)
locationInputView.alpha = 0
UIView.animate(withDuration: 0.5) {
self.locationInputView.alpha = 1
} completion: { _ in
print("DEBUG: Present table view")
UIView.animate(withDuration: 0.3) {
self.tableView.frame.origin.y = self.locationInputView.frame.height
}
}
}
private func configureTableView() {
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.register(LocationTableViewCell.self, forCellReuseIdentifier: LocationTableViewCell.identifier)
tableView.rowHeight = 60
tableView.tableFooterView = UIView()
let height = view.frame.height - locationInputView.frame.height
tableView.frame = CGRect(x: 0, y: view.frame.height, width: view.frame.width, height: height)
}
private func dismissInputView(completion: ((Bool) -> Void)? = nil) {
UIView.animate(withDuration: 0.3, animations: {
self.locationInputView.alpha = 0
self.tableView.frame.origin.y = self.view.frame.height
self.locationInputView.removeFromSuperview()
}, completion: completion)
}
private func removeAnnotationsAndOverlays() {
mapView.annotations.forEach { annotation in
if let anno = annotation as? MKPointAnnotation {
mapView.removeAnnotation(anno)
}
}
if mapView.overlays.count > 0 {
mapView.removeOverlay(mapView.overlays[0])
}
}
}
// MARK:- MapView Functions
extension HomeViewController: MKMapViewDelegate {
private func generatePolyline(toDestination destination: MKMapItem) {
let request = MKDirections.Request()
request.source = MKMapItem.forCurrentLocation()
request.destination = destination
request.transportType = .automobile
let directionRequest = MKDirections(request: request)
directionRequest.calculate { (response, error) in
guard let response = response else { return }
self.route = response.routes[0]
guard let polyline = self.route?.polyline else { return }
self.mapView.addOverlay(polyline)
}
}
private func searchBy(naturalLanguageQuery: String, completion: #escaping([MKPlacemark]) -> Void) {
var results = [MKPlacemark]()
let request = MKLocalSearch.Request()
request.region = mapView.region
request.naturalLanguageQuery = naturalLanguageQuery
let search = MKLocalSearch(request: request)
search.start { (response, error) in
guard let response = response else { return }
response.mapItems.forEach { (item) in
results.append(item.placemark)
}
completion(results)
}
}
// Change driver annotation appearance to Uber arrow
public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? DriverAnnotation {
let view = MKAnnotationView(annotation: annotation, reuseIdentifier: DriverAnnotation.identifier)
view.image = #imageLiteral(resourceName: "chevron-sign-to-right")
return view
}
return nil
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let route = self.route {
let polyline = route.polyline
let lineRenderer = MKPolylineRenderer(overlay: polyline)
lineRenderer.strokeColor = .mainBlueTint
lineRenderer.lineWidth = 3
return lineRenderer
}
return MKOverlayRenderer()
}
}
// MARK:- Location Manager Services
extension HomeViewController {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .notDetermined:
print("DEBUG: Not determined")
locationManager?.requestWhenInUseAuthorization()
case .restricted:
break
case .denied:
break
case .authorizedAlways:
print("DEBUG: Auth always")
locationManager?.startUpdatingLocation()
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
case .authorizedWhenInUse:
print("DEBUG: Auth when in use")
locationManager?.requestAlwaysAuthorization()
#unknown default:
break
}
}
}
// MARK:- Input Activation View Delegate Methods
extension HomeViewController: LocationInputActivationViewDelegate {
func presentLocationInputView() {
configureLocationInputView()
self.inputActivationView.alpha = 0
}
}
// MARK:- Input View Delegate Methods
extension HomeViewController: LocationInputViewDelegate {
func executeSearch(query: String) {
searchBy(naturalLanguageQuery: query) { (results) in
print("DEBUG: Placemarks are \(results)")
self.searchResults = results
self.tableView.reloadData()
}
}
func dismissLocationInputView() {
dismissInputView()
UIView.animate(withDuration: 0.5) {
self.inputActivationView.alpha = 1
}
}
}
// MARK:- TableView Delegate and Datasource Methods
extension HomeViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
2
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "test"
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section == 0 ? 2 : searchResults.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: LocationTableViewCell.identifier, for: indexPath) as! LocationTableViewCell
if indexPath.section == 1 {
cell.placemark = searchResults[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let placemark = self.searchResults[indexPath.row]
configureActionButtonState(config: .dismissActionView)
let destination = MKMapItem(placemark: placemark)
self.generatePolyline(toDestination: destination)
self.dismissInputView { _ in
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
self.mapView.addAnnotation(annotation)
self.mapView.selectAnnotation(annotation, animated: true)
}
}
}
Here's the output in the debug window also, not sure if it's relevant:
2021-01-26 19:36:40.853479+0700 Uber[17163:1579722] [Default] InfoLog PolylineOverlayFillShader: WARNING: 0:42: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:48: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:54: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:66: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:68: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:72: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:74: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:78: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:80: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
Thanks in advance!
I have created a simplified version of this - with a hard-coded destination, but creating the route as you do, and this works very nicely.
private func generatePolyline(toDestination destination: MKMapItem) {
let request = MKDirections.Request()
request.source = MKMapItem.forCurrentLocation()
request.destination = destination
request.transportType = .automobile
let directionRequest = MKDirections(request: request)
directionRequest.calculate { (response, error) in
guard let response = response else { return }
self.route = response.routes[0]
guard let polyline = self.route?.polyline else { return }
self.mapView.addOverlay(polyline)
print("update map?")
}
}
the only thing I have added here is the debug print statement at the end - always nice to know that something should be happening, but always good to know it actually has!
I called the function from a simple test button
#IBAction func cmdButton(_ sender: Any) {
// for testing - Edinburgh Castle
generatePolyline(toDestination: MKMapItem(placemark: MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: 55.9483, longitude: -3.1981), addressDictionary: nil)))
}
The renderer is very simple, and includes a test print as above
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let pr = MKPolylineRenderer(overlay: overlay)
pr.strokeColor = UIColor.red.withAlphaComponent(0.5)
pr.lineWidth = 7
print("rendererFor overlay")
return pr
}
If you're seeing the debug print comments, you should be ok. Let me know how you get on...
After digging a little deeper, the reason is that I'm in Phnom Penh, Cambodia. Apple Maps doesn't have the ability to give directions in Cambodia. Guess I'll have to resort to Google Maps if I want a line drawn from one point to another then.
I am coming to a problem where I try to generate different icons to show on the map view of places. But, I need some help from you guys. So far, I have hard-coded a pin to show on the map view. I also, have different pins in my assets, I want to show them by generating it on the mapview. How can I generate different icons to show on my map view from the API? Thanks for the help.
Here is my code:
import UIKit
import MapKit
import CoreLocation
class MapViewController: BaseViewController{
#IBOutlet weak var leadingConstraints: NSLayoutConstraint!
#IBOutlet weak var menuView: UIView!
#IBOutlet weak var mapView: MKMapView!
fileprivate let locationManager = CLLocationManager()
fileprivate var startedLoadingPOIs = false
fileprivate var places = [Place]()
fileprivate var arViewController: ARViewController!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
var nearMeIndexSelected = NearMeIndexTitle()
var menuShowing = false
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
locationManager.requestWhenInUseAuthorization()
//making shadow of our menu view
menuView.layer.shadowOpacity = 1
menuView.layer.shadowRadius = 7
}
#IBAction func showARController(_ sender: Any) {
arViewController = ARViewController()
arViewController.dataSource = self
arViewController.maxVisibleAnnotations = 30
arViewController.headingSmoothingFactor = 0.05
arViewController.setAnnotations(places)
self.navigationController!.pushViewController(arViewController, animated: true)
}
}
extension MapViewController: CLLocationManagerDelegate, MKMapViewDelegate {
// // Changing the Pin Color on the map.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
mapView.tintColor = #colorLiteral(red: 0.8823529412, green: 0.1647058824, blue: 0.1333333333, alpha: 1)
return nil
} else {
let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
let pin = mapView.view(for: annotation) ?? MKAnnotationView(annotation: annotation, reuseIdentifier: nil)
pin.image = UIImage(named: "pins")
return pin
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.013, longitudeDelta: 0.013)
let region = MKCoordinateRegion(center: location.coordinate, span: span)
mapView.region = region
if !startedLoadingPOIs {
DispatchQueue.main.async {
self.activityIndicator.startAnimating()
}
startedLoadingPOIs = true
let loader = PlacesLoader()
loader.loadPOIS(location: location, radius: 1500) { placesDict, error in
if let dict = placesDict {
guard let placesArray = dict.object(forKey: "results") as? [NSDictionary] else { return }
for placeDict in placesArray {
let latitude = placeDict.value(forKeyPath: "geometry.location.lat") as! CLLocationDegrees
let longitude = placeDict.value(forKeyPath: "geometry.location.lng") as! CLLocationDegrees
let reference = placeDict.object(forKey: "reference") as! String
let name = placeDict.object(forKey: "name") as! String
let address = placeDict.object(forKey: "vicinity") as! String
let location = CLLocation(latitude: latitude, longitude: longitude)
let place = Place(location: location, reference: reference, name: name, address: address)
self.places.append(place)
let annotation = PlaceAnnotation(location: place.location!.coordinate, title: place.placeName)
DispatchQueue.main.async {
self.mapView.addAnnotation(annotation)
}
}
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
self.mapView.isHidden = false
}
}
}
}
}
}
}
}
You can compare the coordinate of the annotation and specify custom pin for that Annotation.
if annotation.coordinate == "Your Custom Pin Coordinate" { //set custom pin }
Suppose I want to add a custom pin for my selected Place.
var selectedPlace: PlaceAnnotation
Inside your loop. suppose my selected place is "toronto"
if name == "toronto" { self.selectedPlace = annotation }
Then in ViewForAnnotation method
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation.coordinate = selectedPlace.coordinate {
pin.image = UIImage(named: "YOUR SELECTED IMAGE")
}
}
See here my demo to create a custom pin view customPinAnnotationButton
Here is the method to draw annotation with image , I subclassed MKAnoationView
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if annotation is MyAnnotation == false
{
return nil
}
let senderAnnotation = annotation as! MyAnnotation
let pinReusableIdentifier = senderAnnotation.pinColor.rawValue
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: pinReusableIdentifier)
if annotationView == nil
{
annotationView = MKAnnotationView(annotation: senderAnnotation, reuseIdentifier: pinReusableIdentifier)
annotationView!.canShowCallout = false
}
if senderAnnotation.pinColor == PinColor.Green
{
let pinImage = UIImage(named:"directMarker3.png")
annotationView!.image = pinImage
}
return annotationView
}
here to add an annotation
let blueLocation = CLLocationCoordinate2D(latitude:30.45454554, longitude: 29.646727)
let blueAnnotation = MyAnnotation(coordinate: blueLocation, title:"ghghhg",subtitle: "hgnhhghghg",pinColor: .Green ,uid:"hghg",type:"provider")
self.mymap.addAnnotation(blueAnnotation)
self.mymap.showAnnotations(self.mymap.annotations, animated: true)
1.Define subclass of MKPointAnnotation:
class MyPointAnnotation: MKPointAnnotation {
var imageName: String = ""
}
2.Set image name.
let annotation = MyPointAnnotation()
annotation.coordinate = coordinate
annotation.title = "title"
annotation.subtitle = "subtitle"
annotation.imageName = "pin" // Set image name here
self.mapView.addAnnotation(annotation)
3.Load image in viewFor delegate method
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let reuseId = "image"
var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId)
if pinView == nil {
pinView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView?.canShowCallout = true
let annotation = annotation as! MyPointAnnotation
pinView?.image = UIImage(named: annotation.imageName)
let rightButton: AnyObject! = UIButton(type: UIButtonType.detailDisclosure)
pinView?.rightCalloutAccessoryView = rightButton as? UIView
}
else {
pinView?.annotation = annotation
}
return pinView
}
I'm trying to display pins on a map, with a preset search parameter. On a button click, the app displays all of the local taxi services. That part I have down...I have the items appending to an [MKPointAnnotation], and it successfully displays a populated list in the tableView as well as pins on the mapView. For some reason I can't get the title and subtitle to display when you tap on the pin on the mapView, even when implementing the viewFor annotation: delegate. Any ideas?
class MapViewController: UIViewController, CLLocationManagerDelegate, UITableViewDataSource, UITableViewDelegate, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var tableView: UITableView!
let locationManager = CLLocationManager()
let regionRadius: CLLocationDistance = 1500
var matchingItems: [MKMapItem] = [MKMapItem]()
var annotationGroup = [MKPointAnnotation]()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
tableView.delegate = self
tableView.dataSource = self
mapView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.allowsSelection = true
let currentLocation = locationManager.location
guard let latitude = currentLocation?.coordinate.latitude,
let longitude = currentLocation?.coordinate.longitude else {
alertPopup(title: "Enable Location Services", message: "Navigate to Settings >", buttonTitle: "Ok")
return
}
let initialLocation = CLLocation(latitude: latitude, longitude: longitude)
tabBarController?.tabBar.isHidden = true
centerMapOnLocation(location: initialLocation)
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = "Taxi"
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start(completionHandler: {(response, error) in
if error != nil {
print("Error occured in search: \(error!.localizedDescription)")
} else if response!.mapItems.count == 0 {
print("No Matches Found")
} else {
print("Matches Found")
}
for location in response!.mapItems {
print("Name = \(String(describing: item.name))")
print("Phone = \(String(describing: item.phoneNumber))")
self.matchingItems.append(item)
var pinAnnotationView = MKPinAnnotationView()
let annotation = MKPointAnnotation()
annotation.coordinate.longitude = location.placemark.coordinate.longitude
annotation.coordinate.latitude = location.placemark.coordinate.latitude
annotation.title? = location.name!
annotation.subtitle = location.phoneNumber!
self.annotationGroup.append(annotation)
self.mapView.addAnnotations(self.annotationGroup)
pinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: nil)
self.mapView.addAnnotation(pinAnnotationView.annotation!)
}
self.tableView.reloadData()
print("Reloaded Table Data")
print(self.matchingItems)
self.mapView.showAnnotations(self.annotationGroup, animated: true)
})
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if !(annotation is MKUserLocation) {
let pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: String(annotation.hash))
let rightButton = UIButton(type: .contactAdd)
rightButton.tag = annotation.hash
pinView.animatesDrop = true
pinView.canShowCallout = true
pinView.rightCalloutAccessoryView = rightButton
return pinView
} else {
print("Returned nil")
return nil
}
}
}
The issue is the optional annotation.title? = location.name!. Remove the optional and it works.
for location in response!.mapItems {
print("Name = \(String(describing: location.name))")
print("Phone = \(String(describing: location.phoneNumber))")
self.matchingItems.append(location)
let annotation = MKPointAnnotation()
annotation.coordinate.longitude = location.placemark.coordinate.longitude
annotation.coordinate.latitude = location.placemark.coordinate.latitude
annotation.title = location.name! //This is the line to remove the optional annotation.title? from.
annotation.subtitle = location.phoneNumber!
self.annotationGroup.append(annotation)
self.mapView.addAnnotations(self.annotationGroup)
self.mapView.showAnnotations(self.annotationGroup, animated: true)
}
Maybe is too late but anyway
First of all, MKAnnotation is a protocol so I think that you must define a custom class for your objects, and this class must implement this protocol or in this case we can define an extension for MKMapItem implementing MKAnnotation protocol
extension MKMapItem : MKAnnotation
{
public var coordinate: CLLocationCoordinate2D {
return self.placemark.coordinate
}
// Title and subtitle for use by selection UI.
public var title: String? {
return self.name
}
public var subtitle: String? {
return self.phoneNumber
}
}
with this your code will be reduced to this
search.start(completionHandler: {(response, error) in
if error != nil {
print("Error occured in search: \(error!.localizedDescription)")
} else if response!.mapItems.count == 0 {
print("No Matches Found")
} else {
print("Matches Found")
}
for location in response!.mapItems {
print("Name = \(String(describing: item.name))")
print("Phone = \(String(describing: item.phoneNumber))")
self.matchingItems.append(item)
}
self.mapView.addAnnotations(self.matchingItems)
self.tableView.reloadData()
print("Reloaded Table Data")
print(self.matchingItems)
self.mapView.showAnnotations(self.matchingItems, animated: true)
})
Once you have this then your implementation of func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {} should work
Hope this helps
For some odd reason the viewForAnnotation is only working for the pin that is set in viewDidLoad (this is a test pin). The pins that are loaded elsewhere aren't getting annotated when pressed. I've already set the delegate. I think it has something to do with the identifier in the mapView call? But I'm unsure of how to fix it. Any help is appreciated! Thanks!
Here's my code:
import Foundation
import UIKit
import MapKit
import CoreLocation
import Alamofire
class MapViewController: UIViewController, MKMapViewDelegate {
var locationManager:CLLocationManager = CLLocationManager()
#IBOutlet weak var potholeMapView: MKMapView!
var listData: Array<String> = []
var idData: Array<Int> = []
var descriptionData: Array<String> = []
var latitudeData:Array<Double> = []
var longitudeData:Array<Double> = []
override func viewDidLoad() {
super.viewDidLoad()
potholeMapView.delegate = self
locationManager.requestWhenInUseAuthorization()
potholeMapView!.region = sanDiegoCountyLocation()
potholeMapView!.mapType = MKMapType.Standard
potholeMapView!.showsUserLocation = true
potholeMapView!.showsTraffic = true
print(potholeMapView!.userLocationVisible)
// WORKING HERE ACCESSORY VIEW SHOWS
let encinitas = CLLocationCoordinate2DMake(32.955, -117.2459)
let marker = AnnotatedLocation(
coordinate: encinitas,
title: "There",
subtitle: "You are not here")
potholeMapView!.addAnnotation(marker)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//HERE ACCESSORY VIEWS DONT SHOW
loadPotholeData()
}
func sanDiegoCountyLocation()-> MKCoordinateRegion {
let center = CLLocationCoordinate2DMake(32.76572795, -117.07319880 )
let widthMeters:CLLocationDistance = 100
let heightMeters:CLLocationDistance = 1000*120
return MKCoordinateRegionMakeWithDistance(center, widthMeters, heightMeters)
}
func loadPotholeData(){
let url = "http://bismarck.sdsu.edu/city/fromDate"
let parametersGet = ["type" : "street", "user" : "008812"]
Alamofire.request(.GET, url, parameters: parametersGet)
.responseJSON { response in
if let dataGet = response.result.value {
let dataDict:NSArray = dataGet as! NSArray
for item in dataDict{
let descrip = item["created"]
self.listData.append(descrip!! as! String)
let ids = item["id"]
self.idData.append(ids! as! Int)
let description = item["description"]
self.descriptionData.append(description!! as! String)
let latitude = item["latitude"]
self.latitudeData.append(latitude as! Double)
let longitude = item["longitude"]
self.longitudeData.append(longitude as! Double)
}
}
else {
print("There was some error getting data")
}
}
createAllPins()
}
func createAllPins(){
for (x, y) in zip(self.latitudeData, self.longitudeData) {
let location = CLLocationCoordinate2DMake(x, y)
let marker = AnnotatedLocation(
coordinate: location,
title: "",
subtitle: "")
potholeMapView!.addAnnotation(marker)
}
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? AnnotatedLocation {
let identifier = "pin"
var view: MKPinAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier)
as? MKPinAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
//view = MKPinAnnotationView(annotation: <#T##MKAnnotation?#>, reuseIdentifier: <#T##String?#>)
view.canShowCallout = true
view.calloutOffset = CGPoint(x: -5, y: 5)
view.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure)
}
return view
}
return nil
}
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
self.performSegueWithIdentifier("pushAnnotation", sender: view)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "pushAnnotation":
let nextVC = segue.destinationViewController as! MapAnnotationDetailViewController
//nextVC.
default: break
}
}
}
}
It appears that it is because your title and subtitle for the others are blank. I tested out having nothing in quotations and adding something, and that seems to fix that issue.
For me, it was that after specifying my custom mapView.mapType, I had to also tell the mapView to "showAnnotations".
Until I told it to show them, the annotations would not display.
mapView.mapType = mapTypes[Preference.mapType]
mapView.showAnnotations(mapView.annotations, animated: true) //this fixed it
I have MKAnnotations set up on a map, but I would like to change the color of the annotations for different scenario's. Is there a way to change the color of the annotation?
Here is my code below, how would I implement the color change?
override func viewDidAppear(animated: Bool) {
var annotationQuery = PFQuery(className: "Post")
currentLoc = PFGeoPoint(location: MapViewLocationManager.location)
//annotationQuery.whereKey("Location", nearGeoPoint: currentLoc, withinMiles: 10)
annotationQuery.whereKeyExists("Location")
annotationQuery.findObjectsInBackgroundWithBlock {
(points, error) -> Void in
if error == nil {
// The find succeeded.
println("Successful query for annotations")
// Do something with the found objects
let myPosts = points as! [PFObject]
for post in myPosts {
let point = post["Location"] as! PFGeoPoint
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2DMake(point.latitude, point.longitude)
annotation.title = post["title"] as! String!
annotation.subtitle = post["username"] as! String!
self.mapView.addAnnotation(annotation)
}
} else {
// Log details of the failure
println("Error: \(error)")
}
}
You can use custom images for annotation view or use predefined MKPinAnnotationView with pinColor. But pinColors limited to Red, Green and Purple.
Some example:
import UIKit
import MapKit
class Annotation: NSObject, MKAnnotation
{
var coordinate: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0)
var custom_image: Bool = true
var color: MKPinAnnotationColor = MKPinAnnotationColor.Purple
}
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self;
let annotation = Annotation.new()
mapView.addAnnotation(annotation)
let annotation2 = Annotation.new()
annotation2.coordinate = CLLocationCoordinate2D(latitude: 0.0, longitude: 1.0)
annotation2.custom_image = false
mapView.addAnnotation(annotation2)
let annotation3 = Annotation.new()
annotation3.coordinate = CLLocationCoordinate2D(latitude: 1.0, longitude: 0.0)
annotation3.custom_image = false
annotation3.color = MKPinAnnotationColor.Green
mapView.addAnnotation(annotation3)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if (annotation is MKUserLocation) {
return nil
}
var anView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if anView == nil {
if let anAnnotation = annotation as? Annotation {
if anAnnotation.custom_image {
let reuseId = "custom_image"
anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
anView.image = UIImage(named:"custom_image")
}
else {
let reuseId = "pin"
let pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView.pinColor = anAnnotation.color
anView = pinView
}
}
anView.canShowCallout = false
}
else {
anView.annotation = annotation
}
return anView
}
}
Update:
Set delegate for mapView in viewDidLoad
You have to set it in the viewForAnnotation method.
In this tutorial is quite well explained how doing it.