Database layout is
root
posts
-postId_1
-userId
-other data
-postId_2
-userId
-other data
When a user wants to scroll through all posts (location irrelevant) I paginate based on postId using:
let postsRef = Database.database().reference().child("posts")
if startKey == nil {
postsRef.queryOrderedByKey().queryLimited(toLast: 20).observeSingleEvent(of: .value) { [weak self](snapshot) in
guard let firstChild = snapshot.children.allObjects.first as? DataSnapshot else { return }
let arr = snapshot.children.allObjects as! [DataSnapshot]
for child in arr.reversed() {
let postId = child.key
guard let dict = child.value as? [String:Any] else { return }
// create a post, append to datasource ...
}
self?.startKey = firstChild.key
}
else {
postsRef.queryOrderedByKey().queryEnding(atValue: startKey!).queryLimited(toLast: 21).observeSingleEvent(of: .value) { (snapshot) in
// same as above ...
}
}
When I want to query other users based on their location in proximity to the current user I use:
let radius = (10 * 2) * 1609.344 // this is 10 miles
guard let currentLocation = locationManager.location else { return }
let lat = currentLocation.coordinate.latitude
let lon = currentLocation.coordinate.longitude
let location = CLLocation(latitude: lat, longitude: lon)
let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: radius, longitudinalMeters: radius)
let geofireRef = Database.database().reference().child("geo")
let geoFire = GeoFire(firebaseRef: geofireRef)
regionQuery = geoFire.query(with: region)
queryHandle = regionQuery?.observe(.keyEntered, with: { [weak self](key: String!, location: CLLocation!) in
let userId = key
let userLocation = location
self?.arrOfUserIds.append(userId)
}
regionQuery?.observeReady({ [weak self] in
self?.paginateBasedOnUserIdReturnedFromRegionQuery()
}
Everything above works fine. The problem is when the regionQuery?.observeReady is called, I have an array of all the userIds in the surrounding 10 mile radius. That can be 1 user or 1000 users and each user can have 1 post or 100+ posts. I can't figure out how to paginate the posts ref based on userId.
Each post has a userId but in the first example above I'm paginating based on postId. I want to paginate the posts ref based on the userIds returned from the regionQuery like
func paginateBasedOnUserIdReturnedFromRegionQuery() {
for userId in arrOfUserIds {
// how to paginate the posts ref using the userId ???
}
}
Related
I have a MapView where you can add or delete MKAnnotation via Firebase .
When you add a new alert it get posted to Firebase on which I have observers both for added and removed snapshots.
Firebase gets updated correctly, map gets updated correctly for added snapshots, but doesn't get updated for deleted ones. I check in both functions that the arrays where I save the alerts are correctly updated before and after receiving the snapshots and they indeed are correct.
So the only thing not happening is the icon removed from the map.. when I use self.mapView.removeAnnotation(annotationToRemove)which I define based on the incoming snapshot.
If I instead remove all the annotations and re add them from the array it works correctly.It's just horrible to see this continuously updating map.. seems more like a glitching error then an updating map.
Can you see why removing the specific one doesn't work??
As always thank you very much.
This is the code:
func getAlerts(setCompletion: #escaping (Bool) -> ()) {
// self.mapView.removeAnnotations(mapView.annotations)
// MapArray.alertNotificationCoordinatesArray.removeAll()
// MapArray.userAlertNotificationArray.removeAll()
print(" MapArray.alertNotificationCoordinatesArray before getAlerts is: \(MapArray.alertNotificationCoordinatesArray)")
print(" MapArray.userAlertNotificationArray before getAlerts is: \(MapArray.userAlertNotificationArray)")
ref = Database.database().reference()
// ref?.child("Continent").child("Europe").child("Country").child("Italy").child("Region").child("Emilia-Romagna").child("City").child("Bologna").child("Community").child("Alert Notifications").observe(.childAdded, with: { (snapshot) in
ref?.child("Continent").child("Europe").child("Country").child("\(String(describing: userDetails.country!))").child("Region").child("\(String(describing: userDetails.region!))").child("City").child("\(String(describing: userDetails.city!))").child("Community").child("Alert Notifications").observe(DataEventType.childAdded, with: { (snapshot) in
// self.mapView.removeAnnotations(self.mapView.annotations) // wrong!! causes all annotations to be deleted when any new one is notified by anyone
// print(" added snapshot is: \(snapshot)")
guard let data = snapshot.value as? [String:String] else { return }
// guard let firebaseKey = snapshot.key as? String else { return }
let firebaseKey = snapshot.key
let dataLatitude = data["Latitude"]!
let dataLongitude = data["Longitude"]!
let type = data["Description"]!
// let id = Int(data["Id"]!)
let id = data["Id"]!
let userName = data["user"]!
let alertImageUrl = data["alertImageUrl"] ?? ""
let alertImageName = data["alertImageName"] ?? ""
let doubledLatitude = Double(dataLatitude)
let doubledLongitude = Double(dataLongitude)
let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)
let userAlertAnnotation = UserAlert(type: type, coordinate: recombinedCoordinate, firebaseKey: firebaseKey, title: type, id: id, userName: userName, alertImageUrl: alertImageUrl, alertImageName: alertImageName)
MapArray.userAlertNotificationArray.append(userAlertAnnotation) // array of notifications coming from Firebase
MapArray.alertNotificationCoordinatesArray.append(recombinedCoordinate) // array for checkig alerts on route
print(" MapArray.alertNotificationCoordinatesArray after getNewerAlerts is: \(MapArray.alertNotificationCoordinatesArray)")
print(" MapArray.userAlertNotificationArray after getNewerAlerts is: \(MapArray.userAlertNotificationArray)")
self.mapView.addAnnotation(userAlertAnnotation)
setCompletion(true)
// self.mapView.addAnnotations(MapArray.userAlertNotificationArray)
})
}
func getDeletedAlerts(setCompletion: #escaping (Bool) -> ()) {
ref?.child("Continent").child("Europe").child("Country").child("\(String(describing: userDetails.country!))").child("Region").child("\(String(describing: userDetails.region!))").child("City").child("\(String(describing: userDetails.city!))").child("Community").child("Alert Notifications").observe(DataEventType.childRemoved, with: { (snapshot) in
print(" MapArray.userAlertNotificationArray before getDeletedAlerts snapshot is: \(MapArray.userAlertNotificationArray)")
print(" MapArray.alertNotificationCoordinatesArray before getDeletedAlerts snapshot is: \(MapArray.alertNotificationCoordinatesArray)")
print(" removed snapshot is: \(snapshot)")
guard let data = snapshot.value as? [String:String] else { return }
let firebaseKey = snapshot.key
let dataLatitude = data["Latitude"]!
let dataLongitude = data["Longitude"]!
let type = data["Description"]!
// let id = Int(data["Id"]!)
let id = data["Id"]!
let userName = data["user"]!
let alertImageUrl = data["alertImageUrl"] ?? ""
let alertImageName = data["alertImageName"] ?? ""
let doubledLatitude = Double(dataLatitude)
let doubledLongitude = Double(dataLongitude)
let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)
let annotationToRemove = UserAlert(type: type, coordinate: recombinedCoordinate, firebaseKey: firebaseKey, title: type, id: id, userName: userName, alertImageUrl: alertImageUrl, alertImageName: alertImageName)
MapArray.userAlertNotificationArray.removeAll(where: { ($0.firebaseKey == firebaseKey) }) //remove the alert
MapArray.alertNotificationCoordinatesArray.removeAll(where: { ($0.latitude == recombinedCoordinate.latitude && $0.longitude == recombinedCoordinate.longitude) })
self.mapView.removeAnnotation(annotationToRemove)
// self.mapView.removeAnnotations(self.mapView.annotations)
// self.mapView.addAnnotations(MapArray.userAlertNotificationArray)
print(" MapArray.userAlertNotificationArray after getDeletedAlerts snapshot is: \(MapArray.userAlertNotificationArray)")
print(" MapArray.alertNotificationCoordinatesArray after getDeletedAlerts snapshot is: \(MapArray.alertNotificationCoordinatesArray)")
setCompletion(true)
})
}
You create the annotation and try to remove it which for sure not added to the mapView
let annotationToRemove = UserAlert(
self.mapView.removeAnnotation(annotationToRemove)
While you should do
for item in self.mapView.annoations {
if let ann = item as? UserAlert , ann.id == annotationToRemove.id {
self.mapView.removeAnnotation(ann)
}
}
I have a function that listen to Firebase for added and removed childs in my database.
I do a print for the array where I store incoming data from database before observers and one after the observers so to check if the logic is working, and it does. Snapshots append and remove the right object to/from the array.
Now I have the problem that on console I get more than one print per call, in both .childAdded and .childRemoved. It's not random, from fresh start the pattern goes like, 1st add to Firebase gives me 2 prints. And from than it's incremental. 2nd add or 1st remove will give me 3 prints. etc etc..
It's almost like it has a counter..
Removing the observers in viewWillDisappeardoesn't change anything..
In Firebase I store MKAnnotation's coordinates. When I add one annotation I post it, I get it back through the observer's snapshotand I add it to the map. When I remove it I remove it from Firebase and I get a snapshot from .childRemovedobserver and I update the map. What I noticed by putting a breakpoint for each observer is that on the.childRemovedit loops incrementally as the prints. That should mean that the annotation gets added that number of times to the map by the .chilAddedobserver right? I thought I solved this but it's so clear that I haven't. Can you spot the problem here?
Many thanks as usual
Here's the function:
func getAlerts(setCompletion: #escaping (Bool) -> ()) {
self.mapView.removeAnnotations(mapView.annotations)
MapArray.alertNotificationCoordinatesArray.removeAll()
MapArray.userAlertNotificationArray.removeAll()
print(" MapArray.alertNotificationCoordinatesArray before getNewerAlerts is: \(MapArray.alertNotificationCoordinatesArray)")
print(" self.userAlertNotificationArray before getNewerAlerts is: \(MapArray.userAlertNotificationArray)")
ref = Database.database().reference()
ref?.child("Continent").child("Europe").child("Country").child("Italy").child("Region").child("Emilia-Romagna").child("City").child("Bologna").child("Community").child("Alert Notifications").observe(.childAdded, with: { (snapshot) in
// self.mapView.removeAnnotations(self.mapView.annotations) //
print(" added snapshot is: \(snapshot)")
guard let data = snapshot.value as? [String:String] else { return }
// guard let firebaseKey = snapshot.key as? String else { return }
let firebaseKey = snapshot.key
// let date = data!["Date"]
// let time = data!["Time"]
let dataLatitude = data["Latitude"]!
let dataLongitude = data["Longitude"]!
let type = data["Description"]!
let id = Int(data["Id"]!)
let userName = data["user"]!
let doubledLatitude = Double(dataLatitude)
let doubledLongitude = Double(dataLongitude)
let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)
let userAlertAnnotation = UserAlert(type: type, coordinate: recombinedCoordinate, firebaseKey: firebaseKey, title: type,id: id!, userName: userName)
MapArray.userAlertNotificationArray.append(userAlertAnnotation) // array of notifications coming from Firebase
MapArray.alertNotificationCoordinatesArray.append(recombinedCoordinate) // array for checkig alerts on route
print(" MapArray.alertNotificationCoordinatesArray after getNewerAlerts is: \(MapArray.alertNotificationCoordinatesArray)")
print(" self.userAlertNotificationArray after getNewerAlerts is: \(MapArray.userAlertNotificationArray)")
setCompletion(true)
self.mapView.addAnnotations(MapArray.userAlertNotificationArray)
})
ref?.child("Continent").child("Europe").child("Country").child("Italy").child("Region").child("Emilia-Romagna").child("City").child("Bologna").child("Community").child("Alert Notifications").observe(.childRemoved, with: { (snapshot) in
print(" self.userAlertNotificationArray before getDeletedAlerts snapshot is: \(MapArray.userAlertNotificationArray)")
print(" MapArray.alertNotificationCoordinatesArray before getDeletedAlerts snapshot is: \(MapArray.alertNotificationCoordinatesArray)")
print(" removed snapshot is: \(snapshot)")
guard let data = snapshot.value as? [String:String] else { return }
let firebaseKey = snapshot.key
// let date = data!["Date"]
// let time = data!["Time"]
let dataLatitude = data["Latitude"]!
let dataLongitude = data["Longitude"]!
let type = data["Description"]!
let id = Int(data["Id"]!)
let userName = data["user"]!
let doubledLatitude = Double(dataLatitude)
let doubledLongitude = Double(dataLongitude)
let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)
_ = UserAlert(type: type, coordinate: recombinedCoordinate, firebaseKey: firebaseKey, title: type,id: id!, userName: userName)
MapArray.userAlertNotificationArray.removeAll(where: { ($0.firebaseKey == firebaseKey) }) //remove the alert
MapArray.alertNotificationCoordinatesArray.removeAll(where: { ($0.latitude == recombinedCoordinate.latitude && $0.longitude == recombinedCoordinate.longitude) })
self.mapView.removeAnnotations(self.mapView.annotations)
self.mapView.addAnnotations(MapArray.userAlertNotificationArray)
print(" self.userAlertNotificationArray after getDeletedAlerts snapshot is: \(MapArray.userAlertNotificationArray)")
print(" MapArray.alertNotificationCoordinatesArray after getDeletedAlerts snapshot is: \(MapArray.alertNotificationCoordinatesArray)")
setCompletion(true)
})
}
Hi I am trying to limit a feed to the most recent two posts from users. I was reading the Firebase documentation and it said simply to add "queryLimited(toFirst: Int)" and so far it has not been working, my feed still retrieves all the posts from each user. At the very top is where I have implemented the "queryLimited".
Edit
The way my code is right now, it is only limiting the users and not the posts(json tree below) so how can I change it to limit the posts of each user?
func getAllPosts(refreshing: Bool, refreshControl: UIRefreshControl?) {
let ref = Database.database().reference().child("posts").queryLimited(toFirst: 2)
MBProgressHUD.showAdded(to: self.view, animated: true)
ref.queryOrdered(byChild: "businessName").observe(.childAdded, with: { snapshot in
//observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? NSDictionary {
self.feeds = []
for item in dict {
let json = JSON(item.value)
let uid = json["uid"].stringValue
var name: String = json["businessName"].stringValue
let address: String = json["businessStreet"].stringValue
let state: String = json["businessCity"].stringValue
let caption: String = json["caption"].stringValue
let downloadURL: String = json["download_url"].stringValue
let timestamp = json["timestamp"].doubleValue
let date = Date(timeIntervalSince1970: timestamp/1000)
let postID: String = json["postID"].stringValue
let lat = json["businessLatitude"].doubleValue
let long = json["businessLongitude"].doubleValue
let businessLocation = CLLocation(latitude: lat, longitude: long)
let latitude = self.locationManager.location?.coordinate.latitude
let longitude = self.locationManager.location?.coordinate.longitude
let userLocation = CLLocation(latitude: latitude!, longitude: longitude!)
let distanceInMeters: Double = userLocation.distance(from: businessLocation)
let distanceInMiles: Double = distanceInMeters * 0.00062137
let distanceLabelText = String(format: "%.2f miles away", distanceInMiles)
self.uid = json["uid"].stringValue
let usersReference = Database.database().reference(withPath: "users").queryOrderedByKey().queryEqual(toValue: uid)
usersReference.observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? NSDictionary {
let userInfo = dict.allValues[0]
let userJSON = JSON(userInfo)
name = userJSON["name"].stringValue
}
let post = Post(uid: uid, caption: caption, downloadURL: downloadURL, name: name, date: date, address: address, state: state, distance: distanceLabelText, postID: postID)
self.feeds.append(post)
// sort posts by date/distance
self.feeds.sort {$0.date.compare($1.date) == .orderedDescending}
self.feeds.sort {$0.distance.compare($1.distance) == .orderedAscending}
self.feedTableView.reloadData()
})
}
}
if refreshing {
refreshControl?.endRefreshing()
}
MBProgressHUD.hide(for: self.view, animated: true)
})
}
I'm pretty sure that the order query must precede the limit query.
let ref = Database.database().reference().child("posts").queryOrdered(byChild: "businessName").queryLimited(toFirst: 2)
I have been knocking my head over this problem for a few days now. I have a function that
1) gets the snapshot values and then stores them into filteredLocations array
2) gets the location the user has pressed on the screen, then measures the distance between the two location points of where they pressed on the screen and the location from the snapshots
3) filters out the locations that meet the users radiusDistance and then places points on the map
The problem that I keep coming across though is with my print statements check 1 and check 2. They are being executed twice when it should only be once.
func addAnnotation(gestureRecognizer:UIGestureRecognizer){
filteredLocations.removeAll()
locations.removeAll()
let sampleRef = FIRDatabase.database().reference().child("SamplePost").child("post")
sampleRef.observeSingleEvent(of:.value, with: {(snapshot) in
if let result = snapshot.children.allObjects as? [FIRDataSnapshot] {
for child in result{
let dictionary = child.value as? [String: AnyObject]
let lat = dictionary?["lat"] as! Double
let long = dictionary?["long"] as! Double
let image = dictionary?["image"] as! String
let text = dictionary?["text"] as! String
let user = dictionary?["user"] as! String
let structure = MapPoints(Latitude: lat, Longitude: long, Image: image, Text: text, User: user)
self.filteredLocations.append(structure)
self.locations.append(structure)
print("check one \(self.filteredLocations.count)")
self.collectionView.reloadData()
}
}
let refined = Double(self.radiusDistanceNumber!)
print("check two \(self.filteredLocations.count)")
if gestureRecognizer.state == UIGestureRecognizerState.began{
self.removePointsAndClearArray()
let touchPoint = gestureRecognizer.location(in: self.mapView)
let newCoordinates = self.mapView.projection.coordinate(for: touchPoint)
let marker = GMSMarker(position: newCoordinates)
marker.title = "Selected location"
self.filteredLocations = self.locations.filter {
(locations) in
let userLocation = newCoordinates
let userLat = userLocation.latitude
let userLong = userLocation.longitude
let coordinateOne = CLLocation(latitude: userLat, longitude: userLong)
let coordinateTwo = CLLocation(latitude: CLLocationDegrees(locations.latitude!), longitude: CLLocationDegrees(locations.longitude!))
let distanceFromPoints = coordinateOne.distance(from: coordinateTwo)
let convertToMiles = distanceFromPoints*0.00062137
return convertToMiles < refined
}
print("check three \(self.filteredLocations.count)")
self.filteredLocations.map {
(location) in
let annotation = GMSMarker()
annotation.position = CLLocationCoordinate2D(latitude: CLLocationDegrees(location.latitude!), longitude: CLLocationDegrees(location.longitude!))
annotation.map = self.mapView
let camera = GMSCameraPosition.camera(withTarget: newCoordinates, zoom: 11)
self.mapView.animate(to: camera)
}
print("amount that is being counted \(self.filteredLocations.count)")
if self.filteredLocations.count <= 0 {
self.collectionView.isUserInteractionEnabled = false
} else{
self.collectionView.isUserInteractionEnabled = true
}
print("amount before reloading collectionView \(self.filteredLocations.count)")
self.collectionView.reloadData()
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn, animations: {
})
}
})
}
Currently, as my codes below shown, my tableview display everything from Firebase. How do I limit the list to what's nearby?
DataService.dataService.BUSINESS_REF.observeEventType(.Value, withBlock: { snapshot in
// A snapshot of the businesses data
self.businesses = []
if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
for snap in snapshots {
// Make business array for the tableview
if let postDictionary = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let business = Business(key: key, dictionary: postDictionary)
// Show newest business first
self.businesses.insert(business, atIndex: 0)
}
}
}
// Update the table when there is new data
self.searchTableView.reloadData()
})
I'm new to iOS programming and the codes above is from a tutorial, I realise I need to make use of GeoFire's GFQuery objects but I just can't figure out where to put this in my code. Thanks in advance!
Figured it out. Hope someone can suggest the better way of doing this.
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
firebase = Firebase(url:"https://myapp.firebaseio.com/")
geoFire = GeoFire(firebaseRef: firebase!.childByAppendingPath("geo"))
let userLocation:CLLocationCoordinate2D = (locationManager.location?.coordinate)!
let center = CLLocation(latitude: userLocation.latitude, longitude: userLocation.longitude)
let span = MKCoordinateSpanMake(0.0125, 0.0125)
let region = MKCoordinateRegionMake(center.coordinate, span)
let regionQuery = geoFire?.queryWithRegion(region)
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
regionQuery!.observeEventType(GFEventTypeKeyEntered, withBlock: { (key: String!, location: CLLocation!) in
// Check for changes in Firebase database
DataService.dataService.BUSINESS_REF.queryOrderedByKey().queryEqualToValue(key).observeEventType(.Value, withBlock: { snapshot in
if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
for snap in snapshots {
if let postDictionary = snap.value as? Dictionary<String, AnyObject> {
//let key = snap.key
let business = Business(key: key, dictionary: postDictionary)
self.businesses.insert(business, atIndex: 0)
}
}
}
self.searchTableView.reloadData()
})
})
}