How to navigate to another page with map annotations in swift - ios

i am trying to naviagate to another page which is my details page for map markers that i have in my map controller view. so for instance if a user clicks on any marker it would take him to that markers details specifically
i created a subclass and initialized the data i need for each marker to be unique.
but now i am facing a problem with how to navigate to the next page since this annotation is not a variable.
here is my code:
my subclass:
class MyAnnotation: MKPointAnnotation{
var shopPID: String!
init(shopPID: String) {
self.shopPID = shopPID
}
}
adding the markers:
db.collection("Shops").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let lat = document["latitude"] as? String
let long = document["longitude"] as? String
let myFloat = (lat! as NSString).doubleValue
let myFloat2 = (long! as NSString).doubleValue
let annotation = MyAnnotation(shopPID: document["shopPID"] as! String)
annotation.coordinate = CLLocationCoordinate2D(latitude: myFloat, longitude: myFloat2)
annotation.title = document["name"] as? String
annotation.subtitle = "Click to view shop details"
annotation.shopPID = document["shopPID"] as? String
self.mapView.addAnnotation(annotation)
}
}
}
performing the click events and the segue:
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
print(#function)
if control == view.rightCalloutAccessoryView {
performSegue(withIdentifier: "mapToDetails", sender: nil)
}
}
here is where i have a problem: i need to give a value to the newProjectVC that is stored in the annotations.
how would i do that?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "mapToDetails") {
let navigationController = segue.destination as! UINavigationController
let newProjectVC = navigationController.topViewController as! detailsSectionViewController
newProjectVC.getKey = //variable with each shopPID for each pin should be here
}
}
any help? thank you
This circle has my identifier "mapsToDetails"

You should use this MKMapViewDelegate method to set a custom action when your annotation is tapped.
First subclass MKAnnotation:
class MyAnnotation: NSObject, MKAnnotation {
var shopPID: String
var coordinate: CLLocationCoordinate2D
let title: String?
let subtitle: String?
init(shopPID: String, coordinate: CLLocationCoordinate2D, title: String, subtitle: String) {
self.shopPID = shopPID
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
}
}
Then, on your MKMapViewDelegate use:
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let annotation = view.annotation as? MyAnnotation else { return }
let uiStoryboard = UIStoryboard(name: "MyStoryboard", bundle: nil)
guard let vc = myStoryboard.instantiateViewController(withIdentifier: "MyViewController") as? MyViewController else { return }
vc.shopPID = annotation.shopPID
present(vc, animated: true)
}
This way you can identify the tapped annotation and pass its values to your desired VC.

Related

Ios how to pass google map userData to next view

I'm trying to pass google map's userData to next view when user tapped custom info window.
first I created model object like so.
import UIKit
import Firebase
struct Team {
var key: String
var teamName: String
var league: String
var lat: Double
var lng: Double
init(snapshot: DataSnapshot) {
self.key = snapshot.key
self.teamName = (snapshot.value as! NSDictionary)["teamName"] as? String
self.league = (snapshot.value as! NSDictionary)["league"] as? String ?? ""
self.lat = (snapshot.value as! NSDictionary)["lat"] as? Double ?? 0
self.lng = (snapshot.value as! NSDictionary)["lng"] as? Double ?? 0
}
}
I fetched database and put userData like so
var teams = [Team?]()
func fetchTeams(){
let teamRef = Database.database().reference().child("teams")
teamRef.observe(.value, with: { (snapshot) in
var result = [Team]()
for child in snapshot.children {
let child = Team(snapshot: child as! DataSnapshot)
result.append(child)
self.teams = result
}
for team in self.teams {
guard let lat = team?.lat else { return }
guard let lng = team?.lng else { return }
let marker: GMSMarker = GMSMarker()
marker.position = CLLocationCoordinate2DMake(lat, lng)
marker.map = self.mapView
marker.userData = team
}
}, withCancel: nil)
}
this func works perfectly so I guess userData has its property correctly.
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
let infoWindow = Bundle.main.loadNibNamed("Marker", owner: self, options: nil)?.first as! MarkerView
infoWindow.teamLabel.text = (marker.userData as! Team).teamName
return infoWindow
}
func mapView(_ mapView: GMSMapView, didTapInfoWindowOf marker: GMSMarker) {
self.performSegue(withIdentifier: cellId, sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == cellId {
let next: NextViewController = segue.destination as! NextViewController
let marker = GMSMarker()
next.team = marker.userData as? Team
}
}
I implemented like above to pass data to NextViewController.
it worked fine till performSegue but next.team is nil. Does anyone know why?
Thank you in advance!
You are having that issue because you are creating an empty GMSMarker and of course this empty GMSMarker don't have any userData, you have to pass the selected GMSMarker as parameter in the performSegue, specifically in the sender parameter, and cast as Team and pass it to your NextViewController
func mapView(_ mapView: GMSMapView, didTapInfoWindowOf marker: GMSMarker) {
self.performSegue(withIdentifier: cellId, sender: marker.userData)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == cellId {
let next: NextViewController = segue.destination as! NextViewController
if let teamData = sender as? Team{
next.team = teamData
debugPrint("teamData is fine")
}
debugPrint("segue identifier is correct")
}
}

Passing data through a segue from a detailDisclosure button in a map callout to a new DetailView

I have populated several pins on a MapView using a JSON file. Each of these pins correctly display a callout with a title, subtitle, image and detailDisclosure button.
I am trying to create a segue between the MapView and a Detail View (arranged as a TableView) so that when users click the detailDisclosure button, they are brought to the Detail View screen.
The segue I have created works perfectly, however, I cannot figure out how to pass the data through. How can I successfully pass data through this segue so that it appears in the Detail View? Please see the relevant code below.
My segue code:
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
self.performSegue(withIdentifier: "toShowLocationDetail", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toShowLocationDetail" {
// I DON'T KNOW WHAT TO PUT IN HERE - I THINK THIS IS WHERE THE INFORMATION ABOUT THE DATA GOES
}
}
I'm not sure if you will require this, but this is my ViewDidLoad method (which I have used to parse the JSON file and populate the annotations:
var locations = [Location]()
override func viewDidLoad() {
super.viewDidLoad()
// parse json
if let locationJson = readLocation(){
if let locationArray = locationJson["locations"] as? [[String:Any]]{
for location in locationArray{
locations.append(Location.init(locationInfo: location))
}
print(locations.count)
}
}
// end parse json
nearMeMap.delegate = self
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.nearMeMap.showsUserLocation = true
// Show annotation
for location in locations {
let annotation = MKPointAnnotation()
annotation.title = location.name
annotation.subtitle = location.type
annotation.coordinate = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
self.nearMeMap.addAnnotation(annotation)
}
}
It is important to note that I already have a functional segue between a TableView and the DetailView. I am now wanting to allow users to access the same DetailView page through a MapView.
The variable set within the DetailView (which currently enables it to show the data from the TableView) is:
var location:Location!
EDIT:
This is the Location.swift class:
class Location: NSObject {
var id: String = ""
var name: String = ""
var type: String = ""
var location: String = ""
var image: String = ""
var activity: String = ""
var isVisited: Bool = false
var rating: String = ""
var latitude: Double = 0.0
var longitude: Double = 0.0
init(locationInfo:[String:Any]) {
self.id = locationInfo["id"] as! String
self.name = locationInfo["name"] as! String
self.type = locationInfo["type"] as! String
self.location = locationInfo["location"] as! String
self.image = locationInfo["image"] as! String
self.activity = locationInfo["activity"] as! String
self.isVisited = locationInfo["isVisited"] as! Bool
self.latitude = locationInfo["latitude"] as! Double
self.longitude = locationInfo["longitude"] as! Double
}
public var coordinate: CLLocationCoordinate2D { get {
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
return coordinate
}
}
}
You have to keep track of the selected Annotation. And then you can use the coordinate property of that annotation for passing to DetailView through your prepare(for:sender:) method.
var selectedAnnotation: MKPointAnnotation?
func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView) {
self.selectedAnnotation = view.annotation as? MKPointAnnotation
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let filteredLocations = locations.filter { (location) -> Bool in
return (location.latitude == self.selectedAnnotation?.coordinate.latitude && location.longitude == self.selectedAnnotation?.coordinate.longitude)
}
let selectedLocation = filteredLocations.first
if segue.identifier == "toShowLocationDetail" {
let destinationViewController = segue.destination as! DetailView
destinationViewController.location = selectedLocation
}
}
And in your DetailView class make the location property optional like: var location: Location?

MapKit and Userdefaults issues ('NSUnknownKeyException', 'setValue for undefined key' when trying to open a view from my Annotation Callout )

I am working on an App where you have access to a Map and you can add some annotations and when you click them a callout appears with a button and if you press it, it will send you to another view. The problem is, when I want to open the View, the App crashes and I see the error I mentioned in the title. I don't know if this is a problem with my Userdefaults or maybe a problem with a function when I prepare a segue to call the view. I appreciate your help and comments. Here is my code...
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
var hospitales = [MKPointAnnotation]()
var imagenes = [UIImage]()
//For UserDafaults data
var nombreHospital = [String]()
var distanciaEntreUbicaciones = [Double]()
struct Location {
let title: String
let latitude: Double
let longitude: Double
}
let locations = [
Location(title: "Hospital Zambrano", latitude: 25.647399800, longitude: -100.334304500),
Location(title: "Christus Mugerza Sur", latitude: 25.589339000, longitude: -100.257724800),
Location(title: "Saint Paul Hospital", latitude: 49.280524700, longitude: -123.128232600)
]
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
mapView.showsUserLocation = true
for location in locations {
//var i = 0
let point = MKPointAnnotation()
point.title = location.title
point.coordinate = CLLocationCoordinate2DMake(location.latitude, location.longitude)
nombreHospital.append(location.title)
//i += 1
//mapView.addAnnotation(point)
if let currentLocation = locationManager.location?.coordinate {
let locationMapPoint = MKMapPointForCoordinate(currentLocation)
let pinMapPoint = MKMapPointForCoordinate(point.coordinate)
let distance = MKMetersBetweenMapPoints(locationMapPoint, pinMapPoint)
UserDefaults.standard.set(distanciaEntreUbicaciones, forKey: "distancia")
UserDefaults.standard.synchronize()
if distance >= 0 && distance <= 45000000 {
let distancia: Double = round (distance / 1000)
distanciaEntreUbicaciones.append(distancia)
point.subtitle = "Dist. \(distancia) kilometros"
mapView.addAnnotation(point)
//Does not perform condition... Or that´s what happens to me
}
}
}
UserDefaults.standard.set(nombreHospital, forKey: "nombre")
UserDefaults.standard.set(distanciaEntreUbicaciones, forKey: "distancia")
UserDefaults.standard.synchronize()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if !(annotation is MKPointAnnotation) {
print("No es de tipo MKPointAnnotation")
}
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "identifier")
if annotationView == nil{
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "identifier")
annotationView!.canShowCallout = true
annotationView!.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
}
else {
annotationView!.annotation = annotation
}
annotationView!.image = UIImage(named: "curz")
return annotationView
}
var anotacionSeleccionada : MKPointAnnotation!
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == view.rightCalloutAccessoryView {
anotacionSeleccionada = view.annotation as? MKPointAnnotation
performSegue(withIdentifier: "vista", sender: self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//I want to open the segue in my DetailView class is it ok
//if I leave the as? ViewController? or should I change it?
if let destination = segue.destination as? ViewController {
//There´s also an error here, I want to open the view for an
//specific annotation, maybe an array will be good
destination.hospitales[] = anotacionSeleccionada
}
}
}
The error:

Pass Image from Map Annotation to Details View Controller

I have class with the name Capital in the separate swift file. In my (ViewController) I declare location, image, title and etc for each of the pins on the map. When pin is tapped calloutAccessoryControlTapped pops up and it directs me to my DetailsViewController, where I passed the title of the tapped pin, but I can't pass an image...
let Headquarters = Capital(title: "Headquarters", coordinate: CLLocationCoordinate2D(latitude: (some latitude), longitude: (some longitude) ), info: "Our headquarters", image: "Location1")
And then I have my perfectly working segue from ViewController to DetailViewController
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetails" {
let theDestination:DetailsViewController = segue.destinationViewController as! DetailsViewController
theDestination.toPass = (sender as! MKAnnotationView).annotation!.title! } }
My Class Capital
import MapKit
import UIKit
class Capital: NSObject, MKAnnotation {
var title: String?
var coordinate: CLLocationCoordinate2D
var info: String
var image: String?
init(title: String, coordinate: CLLocationCoordinate2D, info: String, image: String) {
self.title = title
self.coordinate = coordinate
self.info = info
self.image = image
}
}
And that's how I have it in the ViewController(declaring them)
let Headquarters = Capital(title: "Headquarters", coordinate: CLLocationCoordinate2D(latitude: (some coordinate), longitude: (some coordinate)), info: "Our headquarters", image: "Location1")
And there are several more of them
mapView.addAnnotations([Headquarters, BlahBlah, OtherStuff])
Then I put them into pins
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "Capital"
if annotation.isKindOfClass(Capital.self) {
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier)
if annotationView == nil {
annotationView = MKAnnotationView(annotation:annotation, reuseIdentifier:identifier)
annotationView!.canShowCallout = true
annotationView!.image = UIImage(named: "pin")
let btn = UIButton(type: .DetailDisclosure)
annotationView!.rightCalloutAccessoryView = btn
} else {
annotationView!.annotation = annotation
}
let cpa = annotation as! Capital
annotationView!.image = UIImage(named:cpa.image!)
return annotationView
}
return nil
}
And then I'm trying to pass image to DetailsViewController
I don't see where you are passing the image in your prepareForSegue, did you load it in your detailViewController ?
First I would declare a variable in the detailVC :
var image: UIImage?
and then I would pass the image in your ViewController :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetails" {
let theDestination:DetailsViewController = segue.destinationViewController as! DetailsViewController
theDestination.toPass = (sender as! MKAnnotationView).annotation!.title!
theDestination.image = UIImage(named :"your-image-name")
}
}
And then in the viewDidLoad of the detailVC I would set my variable image to my IBOutlet ImageView :
override func viewDidLoad(){
self.imageView.image = self.image
}

iOS: MapView Annotations not showing for pins

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

Resources