Display Only Mapkit Annotations From Firebase Created Within The Last Hour - swift

I am currently able to pull down all of my annotations from Firebase Firestore and display them with no problem. In my snapshot listener I would like to be able to only display annotations created within the last hour. I've included my code below that isn't working and is still returning all annotations in my Firestore.
let hourAgo = Date().addingTimeInterval(-3600)
db.collection("pins").addSnapshotListener { QuerySnapshot, Error in
guard let documents = QuerySnapshot?.documents else {
print("No documents")
return
}
let annotation = documents.map { QueryDocumentSnapshot -> Pin in
let data = QueryDocumentSnapshot.data()
let latitude = data["latitude"] as? Double ?? 0.0
let longitude = data["longitude"] as? Double ?? 0.0
let eventTitle = data["eventTitle"] as? String ?? ""
let eventSubtitle = data["eventSubtitle"] as? String ?? ""
let lastUpdated = data["lastUpdated"] as? Timestamp ?? Timestamp.init()
let pinDate = lastUpdated.dateValue()
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
if pinDate >= self.hourAgo {
self.displayPins(coordinate: coordinate, eventSubtitle: eventSubtitle, eventTitle: eventTitle)
}else{
return Pin(latitude: latitude, longitude: longitude, eventTitle: eventTitle, eventSubtitle: eventSubtitle)
}
//print(annotation)
return Pin(latitude: latitude, longitude: longitude, eventTitle: eventTitle, eventSubtitle: eventSubtitle)
}
}
}
func displayPins(coordinate: CLLocationCoordinate2D, eventSubtitle: String, eventTitle: String) {
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
annotation.title = eventTitle
annotation.subtitle = eventSubtitle
mapView.addAnnotation(annotation)
}```

Related

SwiftUI async data receive from #escaping closure

I am having trouble with working over data I receive from server.
Each time server sends data it is new coordinates for each user. I am looping over each incoming data, and I want to send the data in completion to receive them on other end. And update model class with them. At the moment I have two users in server. And sometimes the closure passes data two times, but sometimes just one. And interesting thing is that class properties are not updated, at least I dont see them on UI.
This is function I call when data is received. Response is just string I split to get user data.
func updateUserAdrress(response: String, completion: #escaping (Int, Double, Double, String) -> Void){
var data = response.components(separatedBy: "\n")
data.removeLast()
data.forEach { part in
let components = part.components(separatedBy: ",")
let userID = components[0].components(separatedBy: " ")
let id = Int(userID[1])
let latitude = Double(components[1])!
let longitude = Double(components[2])!
let location = CLLocation(latitude: latitude, longitude: longitude)
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
if error == nil {
let placemark = placemarks?[0]
if let thoroughfare = placemark?.thoroughfare, let subThoroughfare = placemark?.subThoroughfare {
let collectedAddress = thoroughfare + " " + subThoroughfare
DispatchQueue.main.async {
completion(id!, latitude, longitude, collectedAddress)
}
}
} else {
print("Could not get address \(error!.localizedDescription)")
}
}
}
}
In this function I try to invoke the changes on objects. As the incoming data from server is different the first time I have splited the functionality, so the correct block of code would be called.
func collectUsers(_ response: String){
if users.count != 0{
updateUserAdrress(response: response) { id, latitude, longitude, address in
if let index = self.users.firstIndex(where: { $0.id == id }){
let user = self.users[index]
user.latitude = latitude
user.longitude = longitude
user.address = address
}
}
}else{
var userData = response.components(separatedBy: ";")
userData.removeLast()
let users = userData.compactMap { userString -> User? in
let userProperties = userString.components(separatedBy: ",")
var idPart = userProperties[0].components(separatedBy: " ")
if idPart.count == 2{
idPart.removeFirst()
}
guard userProperties.count == 5 else { return nil }
guard let id = Int(idPart[0]),
let latitude = Double(userProperties[3]),
let longitude = Double(userProperties[4]) else { return nil }
let collectedUser = User(id: id, name: userProperties[1], image: userProperties[2], latitude: latitude, longitude: longitude)
return collectedUser
}
DispatchQueue.main.async {
self.users = users
}
}
}
As I also need user address when app starts in model I have made simular function to call in init so it would get address for user. That seems to be working fine. But for more context I will add the model to.
class User: Identifiable {
var id: Int
let name: String
let image: String
var latitude: Double
var longitude: Double
var address: String = ""
init(id: Int, name: String, image: String, latitude: Double, longitude: Double){
self.id = id
self.name = name
self.image = image
self.latitude = latitude
self.longitude = longitude
getAddress(latitude: latitude, longitude: longitude) { address in
self.address = address
}
}
func getAddress(latitude: Double, longitude: Double, completion: #escaping (String) -> Void){
let location = CLLocation(latitude: latitude, longitude: longitude)
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
if error == nil {
let placemark = placemarks?[0]
if let thoroughfare = placemark?.thoroughfare, let subThoroughfare = placemark?.subThoroughfare {
let collectedAddress = thoroughfare + " " + subThoroughfare
completion(collectedAddress)
}
} else {
print("Could not get address \(error!.localizedDescription)")
}
}
}
}
And one interesting thing. That when closure receives two times data and I assign them to class, there are no changes on UI.
I made this project on Xcode 13.4.1 because on Xcode 14 there is a bug on MapAnnotations throwing purple warnings on view changes.

When to use compound queries when using GeoHash?

I need to fetch all users within x radius that match the preferences of the current user.
So I started implementing geohash from Firebase, the documentation was great, my problem might be silly but I don't know when to use all my compound queries...(whereField)
I'm specifying them in the "for query in queries" below but I have the feeling It's not the right spot. how do you use compound queries when using geohash?
//MARK: - GET MATCHES WITH GEOHASH
func getMatchesNearMe(radius: Double) {
// Find matches within 50km of my location
let user = UserService.shared.user
let center = CLLocationCoordinate2D(latitude: user.latitude ?? 0, longitude: user.longitude ?? 0)
let radiusInKilometers: Double = radius
// Each item in 'bounds' represents a startAt/endAt pair. We have to issue
// a separate query for each pair. There can be up to 9 pairs of bounds
// depending on overlap, but in most cases there are 4.
let queryBounds = GFUtils.queryBounds(forLocation: center,
withRadius: radiusInKilometers)
let queries = queryBounds.compactMap { (any) -> Query? in
guard let bound = any as? GFGeoQueryBounds else { return nil }
return db.collection("users")
.order(by: "geohash")
.start(at: [bound.startValue])
.end(at: [bound.endValue])
}
var matchingDocs = [Matches]()
// Collect all the query results together into a single list
func getDocumentsCompletion(snapshot: QuerySnapshot?, error: Error?) -> () {
guard let documents = snapshot?.documents else {
print("Unable to fetch snapshot data. \(String(describing: error))")
return
}
print("\nDocs: Count \(documents.count)")
for doc in snapshot!.documents {
var m = Matches()
m.latitude = doc.data()["latitude"] as? Double ?? 0
m.longitude = doc.data()["longitude"] as? Double ?? 0
let coordinates = CLLocation(latitude: m.latitude ?? 0, longitude: m.longitude ?? 0)
let centerPoint = CLLocation(latitude: center.latitude, longitude: center.longitude)
m.id = doc.data()["id"] as? String ?? ""
m.name = doc.data()["name"] as? String ?? ""
m.birthdate = doc.data()["birthdate"] as? Date ?? Date()
m.gender = doc.data()["gender"] as? String ?? ""
m.datingPreferences = doc.data()["datingPreferences"] as? String ?? ""
m.height = doc.data()["height"] as? Int ?? 0
m.imageUrl1 = doc.data()["photo1"] as? String ?? ""
m.imageUrl2 = doc.data()["photo2"] as? String ?? ""
m.imageUrl3 = doc.data()["photo3"] as? String ?? ""
m.imageUrl4 = doc.data()["photo4"] as? String ?? ""
m.imageUrl5 = doc.data()["photo5"] as? String ?? ""
m.imageUrl6 = doc.data()["photo6"] as? String ?? ""
m.Q1day2live = doc.data()["Q1day2live"] as? String ?? ""
m.QlotteryWin = doc.data()["QlotteryWin"] as? String ?? ""
m.QmoneynotanIssue = doc.data()["QmoneynotanIssue"] as? String ?? ""
m.bucketList = doc.data()["bucketList"] as? String ?? ""
m.jokes = doc.data()["jokes"] as? String ?? ""
// We have to filter out a few false positives due to GeoHash accuracy, but
// most will match
let distance = GFUtils.distance(from: centerPoint, to: coordinates)
print("MatchName: \(m.name), distance: \(distance) \tlat: \(m.latitude), \(m.longitude)")
if distance <= radiusInKilometers {
matchingDocs.append(m)
}
} //end for loop
self.matches = matchingDocs
self.usersLoaded = true
}
// After all callbacks have executed, matchingDocs contains the result. Note that this
// sample does not demonstrate how to wait on all callbacks to complete.
for query in queries {
query
.whereField("gender", in: ["Women", "men"])
.whereField("conversations", notIn: [user.name])
//.getDocuments(completion: getDocumentsCompletion)
.addSnapshotListener(getDocumentsCompletion)
}
print("Docs: \(matchingDocs.count)")
}
If you want to add additional conditions to the query you got from GeoFire, you can do so here:
return db.collection("users")
.order(by: "geohash")
.start(at: [bound.startValue])
.end(at: [bound.endValue])
.whereField("gender", isEqualTo: "female")
You may need to add an index for this, so be sure to check the log output for error messages around that (and a link to the Firebase console to quickly create the index).

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

Representing Coordinates from Firebase as Annotations Swift

I am having trouble putting annotations on my map from longitudinal and latitudinal degrees I uploaded to firebase. I want to add an annotation for all of my users but cannot get xcode to recognize my userLatitude and userLongitude variables in another function.
Anything will help!
func retrieveUsers(){
let ref = Database.database().reference()
ref.child("users").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
let users = snapshot.value as! [String: AnyObject]
self.user.removeAll()
for (_,value) in users {
if let uid = value["uid"] as? String {
if uid != Auth.auth().currentUser!.uid {
let userToShow = User()
if let fullName = value["full name"] as? String,
let imagePath = value["urlToImage"] as? String,
//String(format: "%f", self.currentUserLocation?.longitude ?? 0),
let userLongitude = value["long"] as? CLLocationDegrees,
let userLatitude = value["lat"] as? CLLocationDegrees
//why isnt it recognizing the users degrees
{
userToShow.fullName = value["full name"] as? String
userToShow.imagePath = value["urlToImage"] as? String
userToShow.userID = value["uid"] as? String
userToShow.userLongitude = value["long"] as? CLLocationDegrees
userToShow.userLatitude = value["lat"] as? CLLocationDegrees
self.user.append(userToShow)
}
}
}
}
DispatchQueue.main.async {
self.map.reloadInputViews()
//not sure if this is right
}
})
}
func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
let otherUserLocation: CLLocationCoordinate2D = CLLocationCoordinate2DMake(userLatitude, userLongitude)
let userAnnotation = MKPointAnnotation()
userAnnotation.coordinate = otherUserLocation
userAnnotation.title = "fullName"
}
And if you change as? CLLocationDegrees by as? String and manage the cast in CLLocationDegrees afterwards?
Because I think, in Firebase, data is stored as String and not as Double (that is the type of data a CLLocationDegrees type is referring to).

Distance from Current Location to Annotation. (Firebase)

I want to have a the distance from my Currentlocation to a annotation that's in the FireData Base. I tried to make it word but i can't ;(. I would like to have the distance between the two locations in a var. I hope you guys can help me.
func reload(){
//get data
Database.database().reference().child("Rollerbanken").observe(.value, with: { (snapshot) in
for item in snapshot.children{
if let value = snapshot.value as? Dictionary<String, Any> {
for key in value.keys {
if let itemDict = value[key] as? Dictionary<String, AnyObject> {
let annotation = MKPointAnnotation()
annotation.title = itemDict["TypeControle"] as! String
let tijd = itemDict["Tijd"] as! String
annotation.subtitle = "Geplaatst om \(tijd)"
let getLatitude = itemDict["Latitude"] as? String
let getLongitude = itemDict["Longitude"] as? String
if let lat = getLatitude, let long = getLongitude {
annotation.coordinate = CLLocationCoordinate2D(latitude: Double(lat)!, longitude: Double(long)!)
self.map.addAnnotation(annotation)
let directionRequest = MKDirectionsRequest()
directionRequest.source = MKMapItem.forCurrentLocation()
if #available(iOS 10.0, *) {
directionRequest.destination = MKMapItem(placemark: MKPlacemark.init(coordinate: CLLocationCoordinate2DMake(Double(lat)!, Double(long)!)))
} else {
// Fallback on earlier versions
}
directionRequest.transportType = .walking
let direction = MKDirections(request: directionRequest)
direction.calculate(completionHandler: { (response, error) in
if error != nil {
print("Error while build route")
} else {
let route = response?.routes.last
let distance = route?.distance
print(distance)
}
})
}
}
}
}
}
})
}
Here is my Structure:
Try to use this code. Don't forget to enable your current location on map
let directionRequest = MKDirectionsRequest()
directionRequest.source = MKMapItem.forCurrentLocation()
directionRequest.destination = MKMapItem(placemark: MKPlacemark.init(coordinate: CLLocationCoordinate2DMake(YOURPOINTLATITUDE, YOURPOINTLONGITUDE)))
directionRequest.transportType = .walking
let direction = MKDirections(request: directionRequest)
direction.calculate(completionHandler: { (response, error) in
if error != nil {
print("Error while build route")
} else {
let route = response?.routes.last
let distance = route?.distance
I have used similar function, NOTE this was my function therefore it has rider and driver.. however you can change it to use annotation and location from firebase.
if let rideRequestDictionary = snapshot.value as? [String:AnyObject] {
// Getting the rider location and email
if let email = rideRequestDictionary["email"] as? String {
if let lat = rideRequestDictionary["lat"] as? Double{
if let lon = rideRequestDictionary["lon"] as? Double{
// Getting the Driver location and email
let driverCLLocation = CLLocation(latitude: driverLocation.latitude, longitude: driverLocation.longitude)
let riderCLLocation = CLLocation(latitude: lat, longitude: lon)
// getting the distance between the two people
let distance = driverCLLocation.distance(from: riderCLLocation) / 1000
// rounding the distances
let roundedDistance = round(distance * 100) / 100
// putting the rounded distance and email in label
cell.textLabel?.text = "\(email) - \(roundedDistance)km away"
}
}
}