4

I am trying to construct a Map App that can receive user inputs of latitude and longitude coordinates that when entered, will place a pin on a map in a different tab. My FirstVC consists of a button "Add Locations" that segues to OtherVC which the user can input the coordinates. SecondVC consists of the MapView. My initial idea is to have a specific array of coordinates, and any new coordinates will be appended to this array. The execution is where I am lacking, because I am not sure how to transfer this array to the MapView. Here is what I have so far:

For the input of coordinates:

import UIKit
import CoreLocation

class OtherVC: UIViewController {

@IBOutlet weak var latitudeField: UITextField!
@IBOutlet weak var longitudeField: UITextField!

var coordinates = [CLLocationCoordinate2D]()

override func viewDidLoad() {
    super.viewDidLoad()

}


@IBAction func addToMap(_ sender: Any) {
    let lat = Double(latitudeField.text!)
    let long = Double(longitudeField.text!)

    self.coordinates.append(CLLocationCoordinate2D(latitude: lat!, longitude: long!))
}




}

For the MapView:

import UIKit
import MapKit
class MapViewController: UIViewController, MKMapViewDelegate {
@IBOutlet weak var mapView: MKMapView!



var coordinates = [CLLocationCoordinate2D]() {
    didSet {
        // Update the pins
        // Since it doesn't check for which coordinates are new, it you go back to
        // the first view controller and add more coordinates, the old coordinates
        // will get a duplicate set of pins
        for (index, coordinate) in self.coordinates.enumerated() {
            let annotation = MKPointAnnotation()
            annotation.coordinate = coordinate
            annotation.title = "Location \(index)"

            mapView.addAnnotation(annotation)
        }
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    mapView.delegate = self
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    let identifier = "pinAnnotation"
    var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView

    if annotationView == nil {
        annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
        annotationView?.canShowCallout = true
    }

    annotationView?.annotation = annotation
    return annotationView
}
}

enter image description here

Kevin
  • 1,189
  • 2
  • 17
  • 44

2 Answers2

2

I think you need is get your second ViewController MapViewController from your tabBarController and then pass the coordinates array, so in your addToMap Action replace with this

@IBAction func addToMap(_ sender: Any) {
        let lat = Double(latitudeField.text!)
        let long = Double(longitudeField.text!)

        self.coordinates.append(CLLocationCoordinate2D(latitude: lat!, longitude: long!))
        //here we pass the coordinate array to mapViewController
        if let mapViewController = self.tabBarController?.viewControllers?[1] as? MapViewController
        {
            mapViewController.coordinates = self.coordinates
        }
    }

You need also add a navigation controller, like in the picture enter image description here

I hope this helps you

Reinier Melian
  • 20,519
  • 3
  • 38
  • 55
  • I added a break statement into the if-let statement, and it looks like that runs and transfers the coordinates. However, I am getting an error with "mapView.addAnnotation(annotation)", in receiving the coordinates in mapViewController. Not quite sure what the issue is. – Kevin Dec 27 '16 at 03:28
  • Can you post your error?, remember that CLLocation2D is not any float number you must check if is valid, I can improve my answer if you want to – Reinier Melian Dec 27 '16 at 14:20
  • I am getting the error "Thread 1: EXC_BAD_INSTRUCTION".. Could this be related the type of number in ClLocation2D? – Kevin Dec 27 '16 at 22:07
  • This can be related to lack of validation in your textFields, and also the validation of CLLLocation2D, please post your crash estacktrace – Reinier Melian Dec 27 '16 at 22:22
  • Sorry, I am not familiar with what an estacktrace is. Is that the whole console error? – Kevin Dec 27 '16 at 22:38
  • Yes, you can post it? – Reinier Melian Dec 28 '16 at 14:31
  • My error is: "fatal error: unexpectedly found nil while unwrapping an Optional value (lldb)", when I typed in the coordinates. How can I check that CLLocation2D is valid? – Kevin Dec 29 '16 at 01:37
  • You can use a method defined in CoreLocation called CLLocationCoordinate2DIsValid(CLLocation2D) this method receive as parameter an CLLocation2D, I hope this helps you, I will review later but now I am plenty of work, regards – Reinier Melian Dec 29 '16 at 15:19
1

Generally speaking I only use the method of directly passing the data from one ViewController to the next if there is a parent child relationship and I can do it in prepareForSegue or in and unwind (child to parent). Otherwise I think its better to use the publisher subscriber model with Notification. When your coordinate changes you post a Notification:

NotificationCenter.default.post(name: NSNotification.Name("MapViewController.coordinate.updated"), object: self, userInfo: nil)

Now anyone who cares about MapViewController's coordinates changing can listen:

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(coordinateUpdated), name: NSNotification.Name("coordinate.updated"), object: nil)
}

deinit {
    NotificationCenter.default.removeObserver(self)
}

@objc private func coordinateUpdated( notification: Notification) {
    if let source = notification.object as? MapViewController {
        print(source.coordinates)
    }
}

This makes the views loosely coupled and MapViewController doesn't need to care about who needs to be updated; the subscribers are responsible for registering themselves.

Josh Homann
  • 15,933
  • 3
  • 30
  • 33