Set a Pin if GeoFire find a Person in your region - swift

i have got the Persons near my region in GeoFire. (Firebase)
Now I want to set for each Person a pin.
I wrote the whole code in the CL Location Manager function.
See the topic: Can't get near Users by my location with Firebase (GeoFire) and Swift
Now my idea was this:
var queryHandle = circleQuery.observe(.keyEntered, with: { (key: String!, location: CLLocation!) in
print("Key '\(key)' entered the search area and is at location '\(location)'")
let newPin = MKPointAnnotation()
newPin.coordinate = location.coordinate
self.MapView.addAnnotation(newPin)
}
For each User there should be a pin. I wrote this code, which is not working at all:
var queryHandle = circleQuery.observe(.keyEntered, with: { (key: String!, location: CLLocation!) in
let annotation = MKPointAnnotation()
let coord = CLLocationCoordinate2D()
annotation.coordinate = coord
annotation.title = userID
self.MapView.addAnnotation(annotation)
})

Try this
First, get the users location
let center = CLLocation(latitude: users_lat, longitude: users_long)
then define a radius around that center
var circleQuery = geoFire.query(at: center, withRadius: .60) //600 meters
Then to get all of the other users within that circle, perform a query
self.queryHandle = circleQuery.observe(.keyEntered, with: { (key: String?, location: CLLocation?) in
print("Key '\(key)' entered the search area and is at location '\(location)'")
})
Keeping in mind that Key entered events will be fired for all keys initially matching the query as well as any time afterwards that a key enters the query
Don't forget to define queryHandle as a class var.
Also, remember to point geoFire to your firebase when initting
let geofireRef = Database.database().reference()
let geoFire = GeoFire(firebaseRef: geofireRef)
To add pins to the map, just do this for each pin - just like the code in your question
let annotation = MKPointAnnotation()
let coord = CLLocationCoordinate2D(each users location)
annotation.coordinate = coord
annotation.title = "some user name"
mapView.addAnnotation(annotation)

Related

GeoFire not showing entered region swift

I've tried for so many weeks to get it to work. I have read the documentation many times and there seem to be no examples of this online.
Goal:
All I want to be able to do is print to the console all the posts that are within a 10km radius of the current user's location.
Issues: I don't really understand what parameters need to placed into the geoFire.setLocation the user's locations or post location. The documentation only shows a manually entered coordinates. I want to pull mine from firebase and query it for when the user gets within 10km they are printed to the console. Currently, with the code I have nothing is being printed at all.
fileprivate func setupGeoFireLocation() {
let ref = Database.database().reference(withPath: "posts")
ref.observe(.childAdded, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
guard let latitude = dictionary["latitude"] as? String else { return }
guard let longitude = dictionary["longitude"] as? String else { return }
let postLat = (latitude as! NSString).doubleValue
let postLon = (longitude as! NSString).doubleValue
self.geoFire.setLocation(CLLocation(latitude: postLat, longitude: postLat), forKey: "posts")
//Not quite sure what's meant to be the "forKey:" parameter.
})
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation: CLLocation = locations[0] as CLLocation
ref = Database.database().reference()
geoFire = GeoFire(firebaseRef: ref)
let center = CLLocation(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let circleQuery = geoFire.query(at: center, withRadius: 10.0)
_ = circleQuery.observe(.keyEntered, with: { (key, location) in
print(key)
})
circleQuery.observeReady{
print("All initial data has been loaded and events have been fired for circle query!")
}
}
From your comments it seems like you never specifically set the location information through GeoFire's setLocation method. For GeoQueries to work you need to indeed set the location in GeoFire as well. It won't simply work from your existing lat and lon values.
GeoFire queries a separate location, where it associates keys with geohashes of the location info. Please follow the GeoFire documentation on setting a location for a key, retrieving a location, and querying.
I also recommend reading these questions that explain more about GeoFire and how it works:
Filtering results with Geofire + Firebase (shows the data structure that you're missing)
GeoFire query on User location (shows that there are two top-level nodes: one with the geo-info only, and one with the other information. You currently only have the latter.)
Using GeoFire queries in Swift does not give useful output
Android GeoFire onKeyEntered not trigged

Trying to display location data from Firebase to MapKit

searched about this question a lot but couldn't find a simple answer.
I want to be able to read location data from a Firebase database and display them on the MapKit as annotation.
Can you help?
Right now I can display annotations that are hard-coded but can't figure out how to read the database and then create a loop that displays all annotations on the map.
This is the code for showing the annotations.
I want to fill title, latitude, longitude with the relevant data from Firebase.
The Firebase database is as follows: Firebase Database
**The application is already connected with the Firebase!
let points = [
["title": , "latitude": , "longitude": ],
]
for point in points {
let annotation = MKPointAnnotation()
annotation.title = point["title"] as? String
annotation.coordinate = CLLocationCoordinate2D(latitude: point["latitude"] as! Double, longitude: point["longitude"] as! Double)
mapView.addAnnotation(annotation)
}
After suggestion I edited the code and it is like this right now:
let ref = Database.database().reference()
ref.child("x-database-alpha/name").observe(.childAdded, with: { (snapshot) in
let title = (snapshot.value as AnyObject?)!["Title"] as! String?
let latitude = (snapshot.value as AnyObject?)!["Latitude"] as! String?
let longitude = (snapshot.value as AnyObject?)!["Longitude"] as! String?
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: (Double(latitude!))!, longitude: (Double(longitude!))!)
annotation.title = title
self.mapView.addAnnotation(annotation)
})
Check theExact Firebase Database
Right now the code has no error but it doesn't present the annotation!
Sample code:
let ref = Database.database().reference()
ref.child("name").observe(.childAdded, with: { (snapshot) in
let title = (snapshot.value as AnyObject!)!["Title"] as! String!
let latitude = (snapshot.value as AnyObject!)!["Latitude"] as! String!
let longitude = (snapshot.value as AnyObject!)!["Longitude"] as! String!
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: (Double(latitude!))!, longitude: (Double(longitude!))!)
annotation.title = title
self.map.addAnnotation(annotation)
})

Firebase Location to Maps

I am trying to add my locations values from firebase and turn them into annotation within my app. This is the code I have at the minute which is not working! Looking for a helping hand, feel like I am very close to getting this right but I have hit a mind block!
import UIKit
import Firebase
import MapKit
class ViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
let locationsRef = FIRDatabase.database().reference(withPath: "locations")
override func viewDidLoad() {
super.viewDidLoad()
locationsRef.observe(.value, with: { snapshot in
for item in snapshot.children {
guard let locationData = item as? FIRDataSnapshot else { continue }
let locationValue = locationData.value as! [String: Any]
let location = CLLocationCoordinate2D(latitude: locationValue["lat"] as! Double, longitude: locationValue["lng"] as! Double)
let name = locationValue["name"] as! String
self.addAnnotation(at: location, name: name)
}
})
}
func addAnnotation(at location: CLLocationCoordinate2D, name: String) {
// add that annotation to the map
let annotation = MKPointAnnotation()
annotation.title = location["name"] as? String
annotation.coordinate = CLLocationCoordinate2D(latitude: location["latitude"] as! Double, longitude: location["lonitude"] as! Double)
mapView.addAnnotation(annotation)
}
}
My code is now working but I am getting this issue:
Could not cast value of type "NSTaggedPointerString' (0x10a708d10) to 'NSNumber' (0x109d10488).
Relating to this line of code:
let location = CLLocationCoordinate2D(latitude: locationValue["lat"] as! Double, longitude: locationValue["lng"] as! Double)
I have read something about converting a string to double but there is no string in this line? Can anybody help please?
Updated Code Subtitle
Subtitle
Update Error
Error
You are most likely storing the coordinates as String objects in your Firebase database. That's the most common reason that would cause that error. In that case, try the following
let lat = Double(locationValue["lat"] as! String)
let lng = Double(locationValue["lng"] as! String)
let location = CLLocationCoordinate2D(latitude: lat, longitude: lng)
Update
Some location entries of your database are stored as String objects, others as floating point numbers. You should have a consistent structure (i.e all of them be strings or doubles).
Either way, here's a solution for your error:
var location: CLLocationCoordinate2D!
if let lat = locationValue["lat"] as? String {
// stored as String
let lng = Double(locationValue["lng"] as! String)!
location = CLLocationCoordinate2D(latitude: Double(lat)!, longitude: lng)
}
else {
// stored as a floating point number
let lat = locationValue["lat"] as! Double
let lng = locationValue["lng"] as! Double
location = CLLocationCoordinate2D(latitude: lat, longitude: lng)
}
EDIT 2
Just remove the let here

How to draw a route between current location and destination within Mapkit Swift 3?

I am trying to get the latitude en longitude coordinates of the CLLocationManager instance of the current user's location.
I have those two lines of code in my viewWillAppear method:
locationManager = CLLocationManager()
locationManager.requestWhenInUseAuthorization()
Then I have a method called startRoute to calc the route from the current position to some specific annotation on the map. After that I draw the route between those paths. Unfortunately, this is working with two annotations on the map, but I can't get it work with the current location of the user and some annotation.
I tried below script, but when I print print("LAT \(currentLocation.coordinate.latitude)") it will give me 0.0 as result.
func startRoute() {
// Set the latitude and longtitude of the locations (markers)
let sourceLocation = CLLocationCoordinate2D(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude)
let destinationLocation = CLLocationCoordinate2D(latitude: sightseeing.latitude!, longitude: sightseeing.longitude!)
// Create placemark objects containing the location's coordinates
let sourcePlacemark = MKPlacemark(coordinate: sourceLocation, addressDictionary: nil)
let destinationPlacemark = MKPlacemark(coordinate: destinationLocation, addressDictionary: nil)
// MKMapitems are used for routing. This class encapsulates information about a specific point on the map
let sourceMapItem = MKMapItem(placemark: sourcePlacemark)
let destinationMapItem = MKMapItem(placemark: destinationPlacemark)
// Annotations are added which shows the name of the placemarks
let sourceAnnotation = MKPointAnnotation()
sourceAnnotation.title = "Times Square"
if let location = sourcePlacemark.location {
sourceAnnotation.coordinate = location.coordinate
}
let destinationAnnotation = MKPointAnnotation()
destinationAnnotation.title = "Title"
destinationAnnotation.subtitle = "Subtitle"
if let location = destinationPlacemark.location {
destinationAnnotation.coordinate = location.coordinate
}
// The annotations are displayed on the map
self.mapView.showAnnotations([sourceAnnotation,destinationAnnotation], animated: true )
// The MKDirectionsRequest class is used to compute the route
let directionRequest = MKDirectionsRequest()
directionRequest.source = sourceMapItem
directionRequest.destination = destinationMapItem
directionRequest.transportType = .walking
// Calculate the direction
let directions = MKDirections(request: directionRequest)
// The route will be drawn using a polyline as a overlay view on top of the map.
// The region is set so both locations will be visible
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)
}
}
At the top, directly after the class declaration I create the reference to location manager and 'CLLocation':
var locationManager: CLLocationManager!
var currentLocation = CLLocation()
After all, this isn't working. Only when I change the sourceLocation to hardcoded coordinates, it will draw the route to the destination. What do I wrong or what am I missing?
but when I print print("LAT \(currentLocation.coordinate.latitude)") it will give me 0.0 as result
Of course it does. What else would it do? You have a property
var currentLocation = CLLocation()
That is a zero location. You have no code that ever changes this. Therefore, it is always a zero location.
You say you want the
current user's location
but nowhere do you have any code that obtains the user's location.

Assemble a list of users with Geofire/Firebase

I have a class called User, which has a function that gets all nearby food trucks using GeoFire. I've used an observeReadyWithBlock to take the truck IDs returned by GeoFire, and get the rest of their information using Firebase. However, when I go to access one of the trucks from my array of Truck objects after adding their name and description, it looks like xCode is telling me the array is empty.
I am planning on using this array of nearby trucks in other controller classes, to populate tables showing all of the nearby trucks and some basic information to the user.
How can I properly populate my array of Trucks, and what could I be getting wrong based on the code below. Thanks very much!
func getNearbyTrucks(){
//Query GeoFire for nearby users
//Set up query parameters
let center = CLLocation(latitude: 37.331469, longitude: -122.029825)
let circleQuery = geoFire.queryAtLocation(center, withRadius: 100)
circleQuery.observeEventType(GFEventTypeKeyEntered, withBlock: { (key: String!, location: CLLocation!) in
let newTruck = Truck()
newTruck.id = key
newTruck.currentLocation = location
self.nearbyTrucks.append(newTruck)
}) //End truckQuery
//Execute code once GeoFire is done with its' query!
circleQuery.observeReadyWithBlock({
for truck in self.nearbyTrucks{
ref.childByAppendingPath("users/\(truck.id)").observeEventType(.Value, withBlock: { snapshot in
print(snapshot.value["name"] as! String)
truck.name = snapshot.value["name"] as! String
truck.description = snapshot.value["selfDescription"] as! String
let base64String = snapshot.value["profileImage"] as! String
let decodedData = NSData(base64EncodedString: base64String as String, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)
truck.photo = UIImage(data: decodedData!)!
})
}
}) //End observeReadyWithBlock
print(nearbyTrucks[0].id)
//This line gives the error that the array index is out of range
}
The data from Geofire and the rest of your Firebase Database is not simply "gotten" from the database. It is asynchronously loaded and then continuously synchronized. This changes the flow of your code. This is easiest to see by adding some logging:
func getNearbyTrucks(){
//Query GeoFire for nearby users
//Set up query parameters
let center = CLLocation(latitude: 37.331469, longitude: -122.029825)
let circleQuery = geoFire.queryAtLocation(center, withRadius: 100)
print("Before Geoquery")
circleQuery.observeEventType(GFEventTypeKeyEntered, withBlock: { (key: String!, location: CLLocation!) in
print("In KeyEntered block ")
let newTruck = Truck()
newTruck.id = key
newTruck.currentLocation = location
self.nearbyTrucks.append(newTruck)
}) //End truckQuery
print("After Geoquery")
}
The output of the logging will be in a different order from what you may expect:
Before Geoquery
After Geoquery
In KeyEntered block
In KeyEntered block
...
While the Geo-keys and users are being retrieved from the server, the code continues and getNearbyTrucks() exits before any keys or users are returned.
One common way to deal with this is to change the way you think of your code from "first load the trucks, then print the firs truck" to "whenever the trucks are loaded, print the first one".
In code this translates to:
func getNearbyTrucks(){
//Query GeoFire for nearby users
//Set up query parameters
let center = CLLocation(latitude: 37.331469, longitude: -122.029825)
let circleQuery = geoFire.queryAtLocation(center, withRadius: 100)
circleQuery.observeEventType(GFEventTypeKeyEntered, withBlock: { (key: String!, location: CLLocation!) in
let newTruck = Truck()
newTruck.id = key
newTruck.currentLocation = location
self.nearbyTrucks.append(newTruck)
print(nearbyTrucks[0].id)
}) //End truckQuery
//Execute code once GeoFire is done with its' query!
circleQuery.observeReadyWithBlock({
for truck in self.nearbyTrucks{
ref.childByAppendingPath("users/\(truck.id)").observeEventType(.Value, withBlock: { snapshot in
print(snapshot.value["name"] as! String)
truck.name = snapshot.value["name"] as! String
truck.description = snapshot.value["selfDescription"] as! String
let base64String = snapshot.value["profileImage"] as! String
let decodedData = NSData(base64EncodedString: base64String as String, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)
truck.photo = UIImage(data: decodedData!)!
})
}
}) //End observeReadyWithBlock
}
I've moved the printing of the first truck into the block for the key entered event. Depending on the actual code you're trying to run, you'll move it into different places.
A more reusable approach is the one the Firebase Database and Geofire themselves use: you pass a block into observeEventType withBlock: and that block contains the code to be run when a key is available. If you apply the same pattern to you method, it'd become:
func getNearbyTrucks(withBlock: (key: String) -> ()){
//Query GeoFire for nearby users
//Set up query parameters
let center = CLLocation(latitude: 37.331469, longitude: -122.029825)
let circleQuery = geoFire.queryAtLocation(center, withRadius: 100)
circleQuery.observeEventType(GFEventTypeKeyEntered, withBlock: { (key: String!, location: CLLocation!) in
let newTruck = Truck()
newTruck.id = key
newTruck.currentLocation = location
self.nearbyTrucks.append(newTruck)
withBlock(nearbyTrucks[0].id)
}) //End truckQuery
//Execute code once GeoFire is done with its' query!
circleQuery.observeReadyWithBlock({
for truck in self.nearbyTrucks{
ref.childByAppendingPath("users/\(truck.id)").observeEventType(.Value, withBlock: { snapshot in
print(snapshot.value["name"] as! String)
truck.name = snapshot.value["name"] as! String
truck.description = snapshot.value["selfDescription"] as! String
let base64String = snapshot.value["profileImage"] as! String
let decodedData = NSData(base64EncodedString: base64String as String, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)
truck.photo = UIImage(data: decodedData!)!
})
}
}) //End observeReadyWithBlock
}
Here again, you'll want to move the withBlock() callback to a more suitable place depending on your needs.