Ios how to pass google map userData to next view - ios

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

Related

How to navigate to another page with map annotations in swift

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.

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?

Data not being passed through Segue

I have a slightly confusing issue, I am trying to send location data from a tableview through to a mapkit. My tableview loads the various bits of info to list the names of the location, when I click a cell it segues into a navigation view with the data, however it doesnt seem to be sending the data and errors with found nil issue. I did have this same tableview segueing to a different controller and it worked without issue.
This is my prepareForSegue on my main Viewcontroller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mapLocation" {
if let detailsVC = segue.destination as? mapLocation {
if let tr = sender as? newTracks {
detailsVC.track = tr
}
}
}
}
This is the mapkit view to segue to
import UIKit
import MapKit
import CoreLocation
class mapLocation: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var track: newTracks!
override func viewDidLoad() {
super.viewDidLoad()
let sourceloaction = CLLocationCoordinate2D(latitude: track.lat, longitude: track.lon)
let destinationLocation = CLLocationCoordinate2D(latitude: track.lat, longitude: track.lon)
let sourcePlacemark = MKPlacemark(coordinate: sourceloaction, addressDictionary: nil)
let destinationPlacemark = MKPlacemark(coordinate: destinationLocation, addressDictionary: nil)
let sourceMapItem = MKMapItem(placemark: sourcePlacemark)
let destinationMapItem = MKMapItem(placemark: destinationPlacemark)
let sourceAnnotation = MKPointAnnotation()
sourceAnnotation.title = track.name
if let location = sourcePlacemark.location {
sourceAnnotation.coordinate = location.coordinate
}
let destinationAnnotation = MKPointAnnotation()
destinationAnnotation.title = track.name
if let location = destinationPlacemark.location {
destinationAnnotation.coordinate = location.coordinate
}
self.mapView.showAnnotations([sourceAnnotation,destinationAnnotation], animated: true)
let directionRequest = MKDirectionsRequest()
directionRequest.source = sourceMapItem
directionRequest.destination = destinationMapItem
directionRequest.transportType = .automobile
let directions = MKDirections(request: directionRequest)
directions.calculate {
(response, error) -> Void in
guard let response = response else {
if let error = error {
print("Error: \(error)")
}
return
}
let route = response.routes[0]
self.mapView.add((route.polyline), level: MKOverlayLevel.aboveRoads)
let rect = route.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegionForMapRect(rect), animated: true)
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.red
renderer.lineWidth = 4.0
return renderer
}
}
and this is my NewTracks struct where the data is loaded to/from:
import Foundation
import FirebaseDatabase
struct newTracks {
let name: String!
let trackId: Int!
let postcode: String!
let trackType: String!
let locID: Int!
let lat: Double!
let lon: Double!
let phoneNumber: String!
let email: String!
let rating: Double!
let numrating: Double!
let totalrating: Double!
let ref: FIRDatabaseReference?
init(name: String, trackId: Int, postcode: String, trackType: String, trackURL: String, locID: Int, lat: Double, lon: Double, phoneNumber: String, email: String, rating: Double, numrating: Double, totalrating: Double) {
self.name = name
self.trackId = trackId
self.ref = nil
self.postcode = postcode
self.trackType = trackType
self.locID = locID
self.lat = lat
self.lon = lon
self.phoneNumber = phoneNumber
self.email = email
self.rating = rating
self.numrating = numrating
self.totalrating = totalrating
}
init(snapshot: FIRDataSnapshot) {
let snapshotValue = snapshot.value as! [String: AnyObject]
name = snapshotValue["name"] as! String
trackId = snapshotValue["id"]as! Int
postcode = snapshotValue["postcode"]as! String
trackType = snapshotValue["type"]as! String
locID = snapshotValue["locID"]as! Int
lat = snapshotValue["lat"]as! Double
lon = snapshotValue["long"]as! Double
phoneNumber = snapshotValue["phone"]as! String
email = snapshotValue["email"]as! String
rating = snapshotValue["rating"]as! Double
ref = snapshot.ref
numrating = snapshotValue["numrating"] as! Double
totalrating = snapshotValue["totalrating"] as! Double
}
func toAnyObject() -> Any {
return [
"name": name,
"trackId": trackId,
"postcode": postcode,
"trackType": trackType,
"locID": locID,
"lat": lat,
"lon": lon,
"phoneNumber": phoneNumber,
"email": email,
"rating": rating,
"numrating": numrating,
"totalrating": totalrating
]
}
}
The error happens on the line in the mapLocation VC
let sourceloaction = CLLocationCoordinate2D(latitude: track.lat, longitude: track.lon)
the lat and lon values are not being passed for some reason.
On my previous VC that the segue worked fine with the data all I had to do was to add the
var track: newTracks
and this allowed the communication to happen back to where the data is coming from, but for some reason it doesn't appear to be working now. I know it will be something fundamentally simple I have missed.
This is my original segue code that worked to a normal View COntroller, not a UINavigationController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "TrackDetailVC" {
if let detailsVC = segue.destination as? TrackDetailVC {
if let tr = sender as? newTracks {
detailsVC.track = tr
}
}
}
}
Yes, when your viewController is embedded in a UINavigationController you have to work through the parts of the chain until you get to the controller you want. Something like this should work:
override function prepare(for segue: UIStoryBoardSegue, sender: Any?) {
if segue.identifier == “TrackDetailVC” {
if let nav = segue.destinationVC as? UINavigationController {
if let detailsVC = nav.viewControllers[0] as? TrackDetailVC {
if let tr = sender as? newTracks {
detailsVC.track = tr
}
}
}
}
}
sender can't conform to type newTracks. Do some debugging with breakpoints and maybe see why this cast isn't working. Try making a variable called newTracks and explicityly giving it a type, and set it equal to what you want. Then make detailsVc.track = newTracks

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