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
Related
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.
Im new in coding. is there a way to show multiple drivers around the user.
.
I've already figured out how to show a list of drivers near the user thru database, now I want the map to show the drivers near the user.
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
import MapKit
class EmployeeTableViewController: UITableViewController, CLLocationManagerDelegate {
#IBOutlet weak var jobsAvailableMap: MKMapView!
var jobRequests : [DataSnapshot] = []
var locationManager = CLLocationManager()
var employeeLocation = CLLocationCoordinate2D()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
Database.database().reference().child("JobRequests").observe(.childAdded) { (snapshot) in
if let jobRequestDictionary = snapshot.value as? [String:AnyObject] {
if let employeeLat = jobRequestDictionary["employeeLat"] as? Double {
} else {
self.jobRequests.append(snapshot)
self.tableView.reloadData()
}
}
}
Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { (timer) in
self.tableView.reloadData()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let coord = manager.location?.coordinate {
employeeLocation = coord
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return jobRequests.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "jobRequestCell", for: indexPath)
let snapshot = jobRequests[indexPath.row]
if let jobRequestDictionary = snapshot.value as? [String:AnyObject] {
if let email = jobRequestDictionary["email"] as? String {
if let lat = jobRequestDictionary["lat"] as? Double {
if let lon = jobRequestDictionary["lon"] as? Double {
let employeeCLLocation = CLLocation(latitude: employeeLocation.latitude, longitude: employeeLocation.longitude)
let employerCLLocation = CLLocation(latitude: lat, longitude: lon)
let distance = employeeCLLocation.distance(from: employerCLLocation) / 1000
let roundedDistance = round(distance * 100) / 100
cell.textLabel?.text = "\(email) - \(roundedDistance)km away"
}
}
}
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let snapshot = jobRequests[indexPath.row]
performSegue(withIdentifier: "acceptSegue", sender: snapshot)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let acceptVC = segue.destination as? AcceptJobViewController {
if let snapshot = sender as? DataSnapshot {
if let jobRequestDictionary = snapshot.value as? [String:AnyObject] {
if let email = jobRequestDictionary["email"] as? String {
if let lat = jobRequestDictionary["lat"] as? Double {
if let lon = jobRequestDictionary["lon"] as? Double {
acceptVC.requestEmail = email
let location = CLLocationCoordinate2D(latitude: lat, longitude: lon)
acceptVC.requestLocation = location
acceptVC.employeeLocation = employeeLocation
}
}
}
}
}
}
}
#IBAction func logoutTapped(_ sender: Any) {
try? Auth.auth().signOut()
navigationController?.dismiss(animated: true, completion: nil)
}
}
I'm trying to look for tutorials online but most are not connected to Firebase Database.
You can use addAnnotation method.
e.g. (no guarantee you can build the following code)
func addDriverAnnotation(snapshot: DataSnapshot){
if let jobRequestDictionary = snapshot.value as? [String:AnyObject] {
if let email = jobRequestDictionary["email"] as? String {
if let lat = jobRequestDictionary["lat"] as? Double {
if let lon = jobRequestDictionary["lon"] as? Double {
let annotation = MKAnnotation()
annotation.coordinate = CLLocationCoordinate2DMake(lat, lon)
annotation.title = "title"
annotation.subtitle = "subtitle"
self.jobsAvailableMap.addAnnotation(annotation)
}
}
}
}
}
You need to call this method in Database.database()... method.
Database.database().reference().child("JobRequests").observe(.childAdded) { (snapshot) in
if let jobRequestDictionary = snapshot.value as? [String:AnyObject] {
if let employeeLat = jobRequestDictionary["employeeLat"] as? Double {
} else {
self.jobRequests.append(snapshot)
self.tableView.reloadData()
self.addDriverAnnotation(snapshot: snapshot)
}
}
}
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")
}
}
I am newbie in Swift development. I need help to pass in data when the annotation view is clicked. When annotation view is clicked it goes to a view controller called DetailsViewController, but not data is being passed. Can anyone help me solve this by passing in some data/details in my view controller. Thank you for the help. PS: I learn from other developers code. :-).
Here is my code:
import UIKit
import MapKit
protocol UserLocationDelegate {
func userLocation(latitude :Double, longitude :Double)
}
class NearMeMapViewController: ARViewController, ARDataSource, MKMapViewDelegate, CLLocationManagerDelegate {
var nearMeIndexSelected = NearMeIndexTitle ()
var locationManager : CLLocationManager!
var nearMeARAnnotations = [ARAnnotation]()
var nearMeRequests = [NearMeRequest]()
var delegate : UserLocationDelegate!
var place: Place?
override func viewDidLoad() {
super.viewDidLoad()
self.title = nearMeIndexSelected.indexTitle
self.locationManager = CLLocationManager ()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.distanceFilter = kCLHeadingFilterNone
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.dataSource = self
self.headingSmoothingFactor = 0.05
self.maxVisibleAnnotations = 30
getNearMeIndexSelectedLocation()
}
func getNearMeIndexSelectedLocation()
{
let nearMeRequest = MKLocalSearchRequest()
nearMeRequest.naturalLanguageQuery = nearMeIndexSelected.indexTitle
let nearMeregion = MKCoordinateRegionMakeWithDistance(self.locationManager.location!.coordinate, 250, 250)
nearMeRequest.region = nearMeregion
let nearMeSearch = MKLocalSearch(request: nearMeRequest)
nearMeSearch.start { (response : MKLocalSearchResponse?, error :Error?) in
for requestItem in (response?.mapItems)! {
let nearMeIndexRequest = NearMeRequest ()
nearMeIndexRequest.name = requestItem.name
nearMeIndexRequest.coordinate = requestItem.placemark.coordinate
nearMeIndexRequest.address = requestItem.placemark.addressDictionary?["FormattedAddressLines"] as! [String]
nearMeIndexRequest.street = requestItem.placemark.addressDictionary?["Street"] as! String!
nearMeIndexRequest.city = requestItem.placemark.addressDictionary?["City"] as! String
nearMeIndexRequest.state = requestItem.placemark.addressDictionary?["State"] as! String
nearMeIndexRequest.zip = requestItem.placemark.addressDictionary?["ZIP"] as! String
self.nearMeRequests.append(nearMeIndexRequest)
print(requestItem.placemark.name)
}
for nearMe in self.nearMeRequests {
let annotation = NearMeAnnotation(nearMeRequest: nearMe)
self.nearMeARAnnotations.append(annotation)
self.setAnnotations(self.nearMeARAnnotations)
}
}
}
Update you tapBlurButton func
func tapBlurButton(_ sender: UITapGestureRecognizer) {
if let annotationView = sender.view as? NearMeARAnnotationView {
if let detailsVc = storyboard?.instantiateViewController(withIdentifier: "DetailsViewController")
as? DetailsViewController {
detailsVc.place = Place(location: (locationManager.location)!,
reference: "",
name: annotationView.annotationNameLabel.text ?? "",
address: annotationView.annotationAddressLabel.text ?? "")
self.navigationController?.pushViewController(detailsVc, animated: true)
}
}
}
Update your NearMeARAnnotationView class
class NearMeARAnnotationView: ARAnnotationView, CLLocationManagerDelegate {
var annotation: ARAnnotation! //<= ADD this variable.
//>>Other code here
init(annotation : ARAnnotation) {
super .init()
self.annotation = annotation //<= pass data to your new var
//>>Other initialization code here
}
//>> Other code here
}
Update your Details vc code
class DetailsViewController: BaseViewController, UITableViewDelegate, UITableViewDataSource{
var annotation: ARAnnotation!
//>> Other code here
}
Update your new tapBlurButton func
func tapBlurButton(_ sender: UITapGestureRecognizer) {
if let annotationView = sender.view as? NearMeARAnnotationView {
if let detailsVc = storyboard?.instantiateViewController(withIdentifier: "DetailsViewController")
as? DetailsViewController {
detailsVc.annotation = annotationView.annotation
detailsVc.place = Place(location: (locationManager.location)!,
reference: "",
name: annotationView.annotationNameLabel.text ?? "",
address: annotationView.annotationAddressLabel.text ?? "")
self.navigationController?.pushViewController(detailsVc, animated: true)
}
}
}
An then in your DetailsVC you should be able to access all the data which was in your annotation passed to your NearMeARAnnotationView in this way:
let name = annotation.name
let address = annotation.address
directly in DetailsVC
I´m getting this error in my code:
Contextual type 'Void' (aka '()') cannot be used with array literal
How I´m I suppose to solve this? Thanks!
I´m using the MVC model. Here is my code:
let users: [User] = {
let ref = FIRDatabase.database().reference().child("position")
ref.observeSingleEvent(of: .childAdded, with: { (snapshot) in
if let locationDict = snapshot.value as? [String: AnyObject] {
guard let lat = locationDict["latitude"] as? CLLocationDegrees,
let long = locationDict["longitude"] as? CLLocationDegrees else { return }
let position = CLLocationCoordinate2D(latitude: lat, longitude: long)
let userPosition = User(name: "Name", position: position)
return [userPosition] //Here is my error
}
})
}()
override func cellClasses() -> [DatasourceCell.Type] {
return [UserCell.self]
}
override func item(_ indexPath: IndexPath) -> Any? {
return users[indexPath.item]
}
override func numberOfItems(_ section: Int) -> Int {
return users.count
}
}
My user property:
import Foundation
import MapKit
struct User {
let name: String
let position: CLLocationCoordinate2D
}
And my Cell:
import LBTAComponents
import MapKit
import CoreLocation
class UserCell: DatasourceCell, CLLocationManagerDelegate, MKMapViewDelegate {
let distanceSpan: Double = 500
override var datasourceItem: Any? {
didSet {
guard let user = datasourceItem as? User else { return }
nameLabel.text = user.name
MapView.setCenter(user.position, animated: false)
MapView.region = MKCoordinateRegion(center: user.position, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
let locationPin = user.position
let annotation = MKPointAnnotation()
annotation.coordinate = locationPin
MapView.addAnnotation(annotation)
MapView.showAnnotations([annotation], animated: true)
}
}
You are assinging a closure to users, which is declared as type [User] (Array with elements of type User).
As you are using MVC, I assume the code is inside a UIViewController use this (untested, but you get the idea):
import CoreLocation
struct User {
var name:String
var position:CLLocationCoordinate
}
class MyViewController: UIViewController {
var ref = FIRDatabase.database().reference().child("position")
var users: [User]?
override func viewDidLoad() {
super.viewDidLoad()
setupRefObserver()
}
private func setupRefObserver() {
ref.observeSingleEvent(of: .childAdded, with: { (snapshot) in
if let locationDict = snapshot.value as? [String: AnyObject] {
guard let lat = locationDict["latitude"] as? CLLocationDegrees,
let long = locationDict["longitude"] as? CLLocationDegrees else { return }
let position = CLLocationCoordinate2D(latitude: lat, longitude: long)
let userPosition = User(name: "Name", position: position)
users = [userPosition]
}
})
}
}