iOS -Using Pull-To-Refresh How to know when new data is added to Firebase Node based on User Location - ios

My app lets users sell things like sneakers etc. The sneakers that appears in the user's feed is based on the sellers who has posted items that are nearby to the user. I use GeoFire to get the seller's location and everything works fine. When the user uses pullToRefresh if there isn't any new data/sneakers that have been added nearby then there is no need to refresh the list.
The place where I am stumped is when the user pullsToRefresh, how do I determine that new items have been added by either a completely new seller who is nearby or the the same seller's who have added additional pairs of sneakers?
For eg. userA lives in zip code 10463 and there are 2 seller's within a 20 mi radius. Any sneakers that those seller's have for sale will appear in the user's feed. But a 3rd seller can come along and post a pair of sneakers or any of the first 2 seller's can add an additional pair. If the user pullsToRefesh then those items will appear but if nothing is added then pullToRefresh shouldn't do anything.
I don't want to unnecessarily rerun firebase code if I don't have to. The only way to do that would be to first check the postsRef to check to see if any new sneakers were added by the 2 sellers or a completely new seller who is also nearby.
code:
let refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(pullToRefresh), for: .valueChanged)
return refreshControl
}()
#objc func pullToRefresh() {
// if there aren't any new nearby sellers or current sellers with new items then the two lines below shouldn't run
arrOfPosts.removeAll() // this is the array that has the collectionView's data. It gets populated in thirdFetchPosts()
firstGetSellersInRadius(miles: 20.0)
}
override func viewDidLoad() {
super.viewDidLoad()
firstGetSellersInRadius(miles: 20.0) // the user can change the mileage but it's hardcoded for this example
}
// 1. I get the user's location and then get all the nearby sellers
func firstGetSellersInRadius(miles: Double) {
// user's location
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 radiusInMeters = (miles * 2) * 1609.344 // 1 mile == 1609.344 meters
let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: radiusInMeters, longitudinalMeters: radiusInMeters)
let geoFireRef = Database.database().reference().child("geoFire")
regionQuery = geoFireRef?.query(with: region)
queryHandle = regionQuery?.observe(.keyEntered, with: { (key: String!, location: CLLocation!) in
let geoModel = GeoModel()
geoModel.userId = key
geoModel.location = location
self.arrOfNearbySellers.append(geoModel)
})
regionQuery?.observeReady({
self.secondLoopNearbySellersAndGetTheirAddress(self.arrOfNearbySellers)
})
}
// 2. I have to grab the seller's username and profilePic before I show their posts because they're shown along with the post
func secondLoopNearbySellersAndGetTheirAddress(_ geoModels: [GeoModel]) {
let dispatchGroup = DispatchGroup()
for geoModel in geoModels {
dispatchGroup.enter()
if let userId = geoModel.userId {
let uidRef = Database.database().reference().child("users")?.child(userId)
uidRef.observeSingleEvent(of: .value, with: { [weak self](snapshot) in
guard let dict = snapshot.value as? [String: Any] else { dispatchGroup.leave(); return }
let profilePicUrl = dict["profilePicUrl"] as? String
let username = dict["username"] as? String
let userModel = UserModel()
userModel.profilePicUrl = profilePicUrl
userModel.username = username
self?.arrOfSellers.append(userModel)
dispatchGroup.leave()
})
}
}
dispatchGroup.notify(queue: .global(qos: .background)) { [weak self] in
self?.thirdFetchPosts(self!.arrOfSellers)
}
}
// 3. now that I have their address I fetch their posts
func thirdFetchPosts(_ userModels: [UserModel]) {
let dispatchGroup = DispatchGroup()
var postCount = 0
var loopCount = 0
for userModel in userModels {
dispatchGroup.enter()
if let userId = userModel.userId {
let postsRef = Database.database().reference().child("posts")?.child(userId)
postsRef?.observe( .value, with: { [weak self](snapshot) in
postCount = Int(snapshot.childrenCount)
guard let dictionaries = snapshot.value as? [String: Any] else { dispatchGroup.leave(); return }
dictionaries.forEach({ [weak self] (key, value) in
print(key, value)
loopCount += 1
guard let dict = value as? [String: Any] else { return }
let postModel = PostModel(userModel: userModel, dict: dict)
self?.arrOfPosts.append(postModel)
if postCount == loopCount {
dispatchGroup.leave()
postCount = 0
loopCount = 0
}
})
})
}
}
dispatchGroup.notify(queue: .global(qos: .background)) { [weak self] in
self?.fourthRemoveQueryObserverReloadCollectionView()
}
}
// 4. now that I have all the posts inside the arrOfPosts I can show them in the collectionView
func foutrhRemoveQueryObserverReloadCollectionView() {
DispatchQueue.main.async { [weak self] in
self?.arrOfPosts.sort { $0.postDate ?? 0 > $1.postDate ?? 0}
self?.refreshControl.endRefreshing()
if let queryHandle = self?.queryHandle {
self.regionQuery?.removeObserver(withFirebaseHandle: queryHandle)
}
self?.collectionView.reloadData()
self?.arrOfNearbySellers.removeAll()
self?.arrOfSellers.removeAll()
}
}

Related

Why does a GeoFire query sometimes use data from a previous load?

So sometimes someone in entered the search radius is from before, ie someone who was in search radius, but based on the current data in the database is not in the radius. Other times, someone who wasn't in the search radius before but now is, doesn't get printed.
This only happens once each time, ie if I load the app for the second time after the erroneous inclusion or exclusion, the correct array prints.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let databaseRef = Database.database().reference()
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else { return }
print("locations = \(locValue.latitude) \(locValue.longitude)")
latestLocation = ["latitude" : locValue.latitude, "longitude" : locValue.longitude]
let lat = locValue.latitude
let lon = locValue.longitude
dict = CLLocation(latitude: lat, longitude: lon)
print("dict", dict)
if let locationDictionary = latestLocation {
databaseRef.child("people").child(uid).child("Coordinates").setValue(locationDictionary)
let geofireRef = Database.database().reference().child("Loc")
let geoFire = GeoFire(firebaseRef: geofireRef)
print(CLLocation(latitude: lat, longitude: lon),"GGG")
geoFire.setLocation(CLLocation(latitude: lat, longitude: lon), forKey: uid)
}
manager.stopUpdatingLocation()
}
Override func ViewdidLoad() {
super.viewDidLoad()
guard let uid = Auth.auth().currentUser?.uid else { return }
let geofireRef = Database.database().reference().child("Loc")
let geoFire = GeoFire(firebaseRef: geofireRef)
geoFire.getLocationForKey(uid) { (location, error) in
if (error != nil) {
print("An error occurred getting the location for \"Coordinates\": \(String(describing: error?.localizedDescription))")
} else if (location != nil) {
print("Location for \"Coordinates\" is [\(location?.coordinate.latitude), \(String(describing: location?.coordinate.longitude))]")
} else {
print("GeoFire does not contain a location for \"Coordinates\"")
}
}
let query1 = geoFire.query(at: self.dict, withRadius: 3)
query1.observe(.keyEntered, with: { key, location in
print("Key: " + key + "entered the search radius.") ///**this prints keys of users within 3 miles. This is where I see the wrong inclusions or exclusions**
do {
self.componentArray.append(key)
}
print(self.componentArray,"kr")
}
)
}
Here's what I would do for testing and maybe a solution. This is similar to your code but takes some of the unknowns out of the equation; I think we maybe running into an asynchronous issue as well, so give this a try.
In viewDidLoad get the current users position. That position will be used as the center point of the query
self.geoFire.getLocationForKey(uid) { (location, error) in
if (error != nil) {
print("An error occurred getting the location for \"Coordinates\": \(String(describing: error?.localizedDescription))")
} else if (location != nil) {
self.setupCircleQueryWith(center: location) //pass the known location
} else {
print("GeoFire does not contain a location for \"Coordinates\"")
}
}
Once the location var is populated within the closure (so you know it's valid) pass it to a function to generate the query
func setupCircleQueryWith(center: CLLLocation) {
var circleQuery = self.geoFire.queryAtLocation(center, withRadius: 3.0)
self.queryHandle = self.circleQuery.observe(.keyEntered, with: { key, location in
print("Key '\(key)' entered the search area and is at location '\(location)'")
self.myKeyArray.append(key)
})
}
self.queryHandle is a class var we can use to remove the query at a later time. I also set up self.geoFire as a class var that points to Loc.
EDIT
At the very top of your class, add a class var to store the keys
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
var ref: DatabaseReference!
var myKeyArray = [String]()
let queryHandle: DatabaseHandle!
and remember to also add a .keyExited event so you will know when to remove a key from the array when the key exits the area.

Strange behaviour in showing annotations images on map using data coming from Firebase. SWIFT 4.1

The strange behaviour is that when I add a new annotation, either tapped or user location, it gets displayed with the right chosen icon. When MapVC load for the first time, the posts retrieved from Firebase have all the same icon, ( the icon name of the latest one posted. If, after posting a new one, I exit mapViewVc to the menuVC and re enter mapViewVC than every icon is displaying the same icon again, now being my previously posted one.
a Few times it happened the the icons were two different icons, randomly chosen.
I don't understand why the coordinates are taken right but the image is not.
The app flow is:
I have a mapView vc where I can either double tap on screen and get coordinate or code user location coordinate via a button and then get to an chooseIconVc where I have all available icons to choose for the annotation. Once I select one, the icon name get passed back in in mapViewVC in unwindHere() that stores icon name into a variable and coordinates into another. In postAlertNotification those variables get posted to Firebase.
In displayAlerts() the data from Firebase gets stored into variables to initialise an annotation and gets added to mapView.
chosen icon:
#IBAction func unwindHere(sender:UIStoryboardSegue) { // data coming back
if let sourceViewController = sender.source as? IconsViewController {
alertNotificationType = sourceViewController.dataPassed
if tapCounter > 0 {
alertNotificationLatitude = String(describing: alertCoordinates.latitude)
alertNotificationLongitude = String(describing: alertCoordinates.longitude)
postAlertNotification() // post new notification to Firebase
} else {
alertCoordinates = self.trackingCoordinates
alertNotificationLatitude = String(describing: self.trackingCoordinates!.latitude)
alertNotificationLongitude = String(describing: self.trackingCoordinates!.longitude)
postAlertNotification() // post new notification to Firebase
}
}
}
than post:
func postAlertNotification() {
// to set next notification id as the position it will have in array ( because first position is 0 ) we use the array.count as value
let latitude = alertNotificationLatitude
let longitude = alertNotificationLongitude
let alertType = alertNotificationType
let post: [String:String] = [//"Date" : date as! String,
//"Time" : time as! String,
"Latitude" : latitude as! String,
"Longitude" : longitude as! String,
"Description" : alertType as! String]
var ref: DatabaseReference!
ref = Database.database().reference()
ref.child("Community").child("Alert Notifications").childByAutoId().setValue(post)
}
retrieve and display:
func displayAlerts() {
ref = Database.database().reference()
databaseHandle = ref?.child("Community").child("Alert Notifications").observe(.childAdded, with: { (snapshot) in
// defer { self.dummyFunctionToFoolFirebaseObservers() }
guard let data = snapshot.value as? [String:String] else { return }
guard let firebaseKey = snapshot.key as? String else { return }
// let date = data!["Date"]
// let time = data!["Time"]
let dataLatitude = data["Latitude"]!
let dataLongitude = data["Longitude"]!
self.alertIconToDisplay = data["Description"]!
let doubledLatitude = Double(dataLatitude)
let doubledLongitude = Double(dataLongitude)
let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)
print("Firebase post retrieved !")
print("Longitude Actual DataKey is \(String(describing: firebaseKey))")
print("fir long \((snapshot.value!, snapshot.key))")
self.userAlertAnnotation = UserAlert(type: self.alertIconToDisplay!, coordinate: recombinedCoordinate, firebaseKey: firebaseKey)
self.mapView.addAnnotation(self.userAlertAnnotation)
})
}
and
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKAnnotationView(annotation: userAlertAnnotation, reuseIdentifier: "") // CHANGE FOR NEW ANNOTATION : FULL DATA
//added if statement for displaying user location blue dot
if annotation is MKUserLocation{
return nil
} else {
annotationView.image = UIImage(named: alertIconToDisplay!) // choose the image to load
let transform = CGAffineTransform(scaleX: 0.27, y: 0.27)
annotationView.transform = transform
return annotationView
}
}
the variables declarations :
var alertIconToDisplay: String?
var userAlertAnnotation: UserAlert!
var alertNotificationType: String?
var alertNotificationLatitude: String?
var alertNotificationLongitude: String?
UPDATE:
annotation cLass:
import MapKit
class UserAlert: NSObject , MKAnnotation {
var type: String?
var firebaseKey: String?
var coordinate = CLLocationCoordinate2D()
var image: UIImage?
override init() {
}
init(type:String, coordinate:CLLocationCoordinate2D, firebaseKey: String) {
self.type = type
self.firebaseKey = firebaseKey
self.coordinate = coordinate
}
}
After understanding where the problem I was explained how to changed the displayAlert() into
func displayAlerts() { // rajish version
ref = Database.database().reference()
databaseHandle = ref?.child("Community").child("Alert Notifications").observe(.childAdded, with: { (snapshot) in
// defer { self.dummyFunctionToFoolFirebaseObservers() }
guard let data = snapshot.value as? [String:String] else { return }
guard let firebaseKey = snapshot.key as? String else { return }
// 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 doubledLatitude = Double(dataLatitude)
let doubledLongitude = Double(dataLongitude)
let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)
print("Firebase post retrieved !")
print("Longitude Actual DataKey is \(String(describing: firebaseKey))")
print("fir long \((snapshot.value!, snapshot.key))")
var userAlertAnnotation = UserAlert(type: type, coordinate: recombinedCoordinate, firebaseKey: firebaseKey, title: type,id: id!)
self.userAlertNotificationArray.append(userAlertAnnotation) // array of notifications coming from Firebase
print("user alert array after append from Firebase is : \(self.userAlertNotificationArray)")
self.alertNotificationArray.append(recombinedCoordinate) // array for checkig alerts on route
self.mapView.addAnnotation(userAlertAnnotation)
})
}
and the mapView to:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { // rajish version
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "")
if annotation is MKUserLocation{
return nil
} else {
print(annotation.coordinate)
annotationView.image = UIImage(named:(annotationView.annotation?.title)! ?? "")
// annotationView.canShowCallout = true
let transform = CGAffineTransform(scaleX: 0.27, y: 0.27)
annotationView.transform = transform
return annotationView
}
}
that solved it.

Swift & Firebase - Observer returning nil after changing data

A have a problem in my app. When i initialize the app on the first ViewController i get some data from my Firebase server using this code and a object called "By" and an array of objects called "byer":
func download() {
byer.removeAll()
self.Handle = self.ref?.child("Byer").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let by = By()
by.Latitude = dictionary["Latitude"]?.doubleValue
by.Longitude = dictionary["Longitude"]?.doubleValue
by.Name = snapshot.key
let coordinate = CLLocation(latitude: by.Latitude!, longitude: by.Longitude!)
let distanceInMeter = coordinate.distance(from: self.locationManager.location!)
by.Distance = Int(distanceInMeter)
byer.append(by)
byer = byer.sorted(by: {$0.Distance! < $1.Distance! })
DispatchQueue.main.async {
selectedCity = byer[0].Name!
self.performSegue(withIdentifier: "GoToMain", sender: nil)
}
}
})
}
This all works fine. But the problem comes when i later in the app chance the value in the database. I use a button with this code:
if byTextfield.text != "" && latitude != nil && longitude != nil {
ref?.child("Byer").child(byTextfield.text!).child("Latitude").setValue(latitude)
ref?.child("Byer").child(byTextfield.text!).child("Longitude").setValue(longitude)
}
But for some reason the app crashes and a red line comes over the line:
let coordinate = CLLocation(latitude: by.Latitude!, longitude: by.Longitude!)
From the download function in the top. And the text:
"Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value.".
I have tried to remove the observer using:
override func viewDidDisappear(_ animated: Bool) {
self.ref?.removeObserver(withHandle: self.Handle)
}
But this dosn't seems to help. Any suggestions?
using guard statement you can easily handle the nil value of the longitude and latitude. i.e
func download() {
byer.removeAll()
self.Handle = self.ref?.child("Byer").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let by = By()
guard let latitude = dictionary["Latitude"]?.doubleValue,let longitude =
dictionary["Longitude"]?.doubleValue else
{
return
}
by.Latitude = latitude
by.Longitude = longitude
by.Name = snapshot.key
let coordinate = CLLocation(latitude: by.Latitude!, longitude: by.Longitude!)
let distanceInMeter = coordinate.distance(from: self.locationManager.location!)
by.Distance = Int(distanceInMeter)
byer.append(by)
byer = byer.sorted(by: {$0.Distance! < $1.Distance! })
DispatchQueue.main.async {
selectedCity = byer[0].Name!
self.performSegue(withIdentifier: "GoToMain", sender: nil)
}
}
})
}
and if you want to unregister the observer from the firebase database reference then remove the database handler at the end of the childadded block.

Activity Indicator for UICollectionView [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I have a UICollectionView that reads data from Firebase, and I want some kind of activity indicator while it's reading data.
I want the activity indicator to start running as soon as the UICollectionView tab in the TabBar is hit, and I want it to stop and the view/uicollectionView to load once loading from Firebase is done.
I saw this post:
Show Activity Indicator while data load in collectionView Swift
But I could not understand it fully because I did not know how to integrate my UICollectionView there.
EDIT:
This is my code that reads from Firebase:
self.ref = Database.database().reference()
let loggedOnUserID = Auth.auth().currentUser?.uid
if let currentUserID = loggedOnUserID
{
// Retrieve the products and listen for changes
self.databaseHandle = self.ref?.child("Users").child(currentUserID).child("Products").observe(.childAdded, with:
{ (snapshot) in
// Code to execute when new product is added
let prodValue = snapshot.value as? NSDictionary
let prodName = prodValue?["Name"] as? String ?? ""
let prodPrice = prodValue?["Price"] as? Double ?? -1
let prodDesc = prodValue?["Description"] as? String ?? ""
let prodURLS = prodValue?["MainImage"] as? String
let prodAmount = prodValue?["Amount"] as? Int ?? 0
let prodID = snapshot.key
let prodToAddToView = Product(name: prodName, price: prodPrice, currency: "NIS", description: prodDesc, location: "IL",
toSell: false, toBuy: false, owner: currentUserID, uniqueID: prodID, amount: prodAmount, mainImageURL: prodURLS)
self.products.append(prodToAddToView)
DispatchQueue.main.async
{
self.MyProductsCollection.reloadData()
}
}
) // Closes observe function
let activityView = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
// waiy until main view shows
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// create a hover view that covers all screen with opacity 0.4 to show a waiting action
let fadeView:UIView = UIView()
fadeView.frame = self.view.frame
fadeView.backgroundColor = UIColor.whiteColor()
fadeView.alpha = 0.4
// add fade view to main view
self.view.addSubview(fadeView)
// add activity to main view
self.view.addSubview(activityView)
activityView.hidesWhenStopped = true
activityView.center = self.view.center
// start animating activity view
activityView.startAnimating()
self.ref = Database.database().reference()
let loggedOnUserID = Auth.auth().currentUser?.uid
if let currentUserID = loggedOnUserID
{
// Retrieve the products and listen for changes
self.databaseHandle = self.ref?.child("Users").child(currentUserID).child("Products").observeSingleEvent(.value, with:
{ (snapshot) in
// Code to execute when new product is added
let prodValue = snapshot.value as? NSDictionary
let prodName = prodValue?["Name"] as? String ?? ""
let prodPrice = prodValue?["Price"] as? Double ?? -1
let prodDesc = prodValue?["Description"] as? String ?? ""
let prodURLS = prodValue?["MainImage"] as? String
let prodAmount = prodValue?["Amount"] as? Int ?? 0
let prodID = snapshot.key
let prodToAddToView = Product(name: prodName, price: prodPrice, currency: "NIS", description: prodDesc, location: "IL",
toSell: false, toBuy: false, owner: currentUserID, uniqueID: prodID, amount: prodAmount, mainImageURL: prodURLS)
self.products.append(prodToAddToView)
DispatchQueue.main.async
{
self.MyProductsCollection.reloadData()
// remove the hover view as now we have data
fadeView.removeFromSuperview()
// stop animating the activity
self.activityView.stopAnimating()
}
}
) // Closes observe function
I mean UIActivityIndicatorView isn't hard to work with, you display it when fetching data, and stop it when done.
private lazy var indicator : UIActivityIndicatorView = { // here we simply declaring the indicator along some properties
let _indicator = UIActivityIndicatorView()
// change the color
_indicator.color = .black
// when you call stopAnimation on this indicator, it will hide automatically
_indicator.hidesWhenStopped = true
return _indicator
}()
Now where you want to place it? you can either placed it into your parent's view, or into your navigationBar. (I choose to place into the right side of the navigationBar )
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(customView: indicator)
Now say you have this function that return data (via callbacks) from some apis.
// this callback emits data from a background queue
func fetchPost(completion:#escaping(Array<Any>?, Error?) -> ()) {
DispatchQueue.global(qos: .background).async {
// ... do work
completion([], nil) // call your completionHandler based either error or data
}
}
/* now let's called that fetchPost function and load data into your
collectionView, but before let's started this indicator */
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(customView: indicator)
indicator.startAnimating()
fetchPost { [weak self] (data, err) in
// go to the main queue to update our collectionView
// and stop the indicator
DispatchQueue.main.async { [weak self] in
// stop animation
self?.indicator.startAnimating()
// do something we have an error
if let _err = err {}
if let _data = data {
// fill array for collectionView
// reload the collectionView
self?.collectionView?.reloadData()
}
}
}
}

How do I write a completion handler for firebase data?

So I had issues previously working with 'observe' from firebase, and I realised I could not bring the variable values from inside the code block that was working asynchronously. A user told me to use completion handlers to resolve this issue, and his example was:
func mapRegion(completion: (MKCoordinateRegion)->()) {
databaseHandle = databaseRef.child("RunList").child(runName).observe(.value, with: { (snapshot) in
let runData = snapshot.value as? [String: AnyObject]
self.minLat = runData?["startLat"] as? Double
self.minLng = runData?["startLong"] as? Double
self.maxLat = runData?["endLat"] as? Double
self.maxLng = runData?["endLong"] as? Double
print("testing")
print(self.minLat!)
print(self.maxLng!)
let region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: (self.minLat! + self.maxLat!)/2,
longitude: (self.minLng! + self.maxLng!)/2),
span: MKCoordinateSpan(latitudeDelta: (self.maxLat! - self.minLat!)*1.1,
longitudeDelta: (self.maxLng! - self.minLng!)*1.1))
completion(region)
})
}
and to use the code:
mapRegion() { region in
mapView.region = region
// do other things with the region
}
So I've tried to recreate this for another method that I need to return an array of object type RunDetail:
func loadRuns(completion: ([RunDetail]) -> ()) {
// we need name, distance, time and user
databaseHandle = databaseRef.child("RunList").observe(.value, with: { (snapshot) in
self.count = Int(snapshot.childrenCount)
print(self.count!)
// more stuff happening here to add data into an object called RunDetail from firebase
// add RunDetail objects into array called 'run'
})
completion(runs)
}
I am not sure if I am setting this up correctly above^.
I still cannot get my head around getting the completion handler working (I really don't understand how to set it up). Can someone please help me and let me know if I am setting this up properly? Thanks.
You need to move the completion(region) to inside the Firebase completion block and add #escaping after completion:.
Also, you should not force unwrap optionals. It is easy enough to check that they are not nil and this will prevent the app from crashing.
func mapRegion(completion: #escaping (MKCoordinateRegion?) -> Void) {
let ref = Database.database().reference()
ref.child("RunList").child(runName).observe(.value, with: { (snapshot) in
guard
let runData = snapshot.value as? Dictionary<String,Double>,
let minLat = runData["startLat"],
let minLng = runData["startLong"],
let maxLat = runData["endLat"],
let maxLng = runData["endLong"]
else {
print("Error! - Incomplete Data")
completion(nil)
return
}
var region = MKCoordinateRegion()
region.center = CLLocationCoordinate2D(latitude: (minLat + maxLat) / 2, longitude: (minLng + maxLng) / 2)
region.span = MKCoordinateSpanMake((maxLat - minLat) * 1.1, (maxLng - minLng) * 1.1)
completion(region)
})
}
Then update your code to this.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
mapRegion { (region) in
if let region = region {
self.mapView.setRegion(region, animated: true)
}
}
}
For your loadRuns
func loadRuns(completion: #escaping (Array<RunDetail>) -> Void) {
let ref = Database.database().reference()
ref.child("RunList").observe(.value, with: { (snapshot) in
var runs = Array<RunDetail>()
// Populate runs array.
completion(runs) // This line needs to be inside this closure.
})
}

Resources