I’ve solved this by also using a way to retrieve coordinates of a place from an address.
It was a little hard to get things straight with the button since both getting the current location and resolving coordinates from addresses are asynchronous operations.
Here’s the code:
//
// MapViewController.swift
// WorldTrotter
//
import UIKit
import MapKit
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
var mapView: MKMapView!
var toggleButton: UIButton!
private var locationManager: CLLocationManager!
private var annotations: [MKAnnotation] = []
private var currentAnnotationIndex: Int?
// MARK: - UIVIewController
override func loadView() {
self.mapView = MKMapView()
self.mapView.delegate = self
self.view = mapView
self.locationManager = CLLocationManager()
let segmentedControl = UISegmentedControl(items: ["Standard", "Hybrid", "Satellite"])
segmentedControl.backgroundColor = UIColor.white.withAlphaComponent(0.5)
segmentedControl.selectedSegmentIndex = 0
segmentedControl.addTarget(self, action: #selector(MapViewController.mapTypeChanged(_:)), for: .valueChanged)
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(segmentedControl)
let topConstraint = segmentedControl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
let margins = view.layoutMarginsGuide
let leadingConstraint = segmentedControl.leadingAnchor.constraint(equalTo: margins.leadingAnchor)
let trailingConstraint = segmentedControl.trailingAnchor.constraint(equalTo: margins.trailingAnchor)
topConstraint.isActive = true
leadingConstraint.isActive = true
trailingConstraint.isActive = true
self.initLocalizationButton(under: segmentedControl)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.createDefaultMapAnnotations()
print("MapViewController loaded its view")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.locationManager.requestWhenInUseAuthorization()
self.mapView.showsUserLocation = true
self.updateButtonTitle()
}
// MARK: - MKMapViewDelegate
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
var idx: Int?
for (index, annotation) in self.annotations.enumerated() where annotation.title == "Current location" {
idx = index
}
if let idx = idx {
let annotation = self.annotations[idx]
self.mapView.removeAnnotation(annotation)
self.annotations.remove(at: idx)
}
let currentLocationPoint = MKPointAnnotation()
currentLocationPoint.coordinate = userLocation.coordinate
currentLocationPoint.title = "Current location"
self.annotations.append(currentLocationPoint)
self.mapView.addAnnotation(currentLocationPoint)
if self.currentAnnotationIndex == nil {
self.currentAnnotationIndex = 0
self.updateButtonTitle()
}
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
return MKPinAnnotationView(annotation: annotation, reuseIdentifier: "PinView")
}
// MARK: - Actions
@objc func mapTypeChanged(_ segControl: UISegmentedControl) {
switch segControl.selectedSegmentIndex {
case 0:
self.mapView.mapType = .standard
case 1:
self.mapView.mapType = .hybrid
case 2:
self.mapView.mapType = .satellite
default:
break
}
}
@objc func showLocalization(_ sender: UIButton) {
if let currentIndex = self.currentAnnotationIndex {
let annotation = self.annotations[currentIndex]
let (latitudinalMeters, longitudinalMeters) = { () -> (CLLocationDistance, CLLocationDistance) in
switch annotation.title {
case "Current location":
return (500, 500)
default :
return (1000, 1000)
}
}()
let zoomedInLocation = MKCoordinateRegion(center: annotation.coordinate, latitudinalMeters: latitudinalMeters, longitudinalMeters: longitudinalMeters)
mapView.setRegion(zoomedInLocation, animated: true)
self.updateButtonTitle(after: currentIndex)
}
}
// MARK: - Helpers
private func initLocalizationButton(under anyView: UIView) {
let localizationButton = UIButton.init(type: .system)
localizationButton.setTitle("", for: .normal)
localizationButton.addTarget(self, action: #selector(MapViewController.showLocalization(_:)), for: .touchUpInside)
localizationButton.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(localizationButton)
let topConstraint = localizationButton.topAnchor.constraint(equalTo: anyView.topAnchor, constant: 32)
let leadingConstraint = localizationButton.leadingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.leadingAnchor)
let trailingConstraint = localizationButton.trailingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.trailingAnchor)
topConstraint.isActive = true
leadingConstraint.isActive = true
trailingConstraint.isActive = true
self.toggleButton = localizationButton
self.toggleButton.isEnabled = false
self.toggleButton.isHidden = true
}
private func getCoordinates(address: String, completionHandler: @escaping (CLLocationCoordinate2D, NSError?) -> Void) {
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address) { (placemarks, error) in
if error == nil {
if let placemark = placemarks?[0] {
let location = placemark.location!
completionHandler(location.coordinate, nil)
return
}
}
completionHandler(kCLLocationCoordinate2DInvalid, error as NSError?)
}
}
private func createDefaultMapAnnotations() {
let addresses: [String : String] = ["Born place" : "Rome, Italy", "Interesting place" : "San Diego, California, USA"]
for title in addresses.keys {
let address = addresses[title]!
self.getCoordinates(address: address) { [unowned self](coordinates, error) in
if error == nil {
let point = MKPointAnnotation()
point.coordinate = coordinates
point.title = title
self.annotations.append(point)
self.mapView.addAnnotation(point)
if self.currentAnnotationIndex == nil {
self.currentAnnotationIndex = 0
self.updateButtonTitle()
}
print("Created map annotation for \(title): \(point)")
} else {
print("Error while retrieving coordinates for: \(title)\n\(error?.localizedDescription ?? "Undefined error")")
}
}
}
}
private func updateButtonTitle(after index: Int? = nil) {
if let currentIndex = index {
self.currentAnnotationIndex = currentIndex < self.annotations.count - 1 ? currentIndex + 1 : 0
}
if let idx = self.currentAnnotationIndex, let title = self.annotations[idx].title {
let buttonTitle = "Move to \(title!)".capitalized
self.toggleButton.setTitle((buttonTitle), for: .normal)
self.toggleButton.isHidden = false
self.toggleButton.isEnabled = true
} else {
self.toggleButton.setTitle("", for: .normal)
self.toggleButton.isEnabled = false
self.toggleButton.isHidden = true
}
}
}