Gold Challenge - my approach


#1

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
        }
    }
}