0

I have been trying to find a solution but no luck :/ I want to call my firebase data strings and use them as the "title" and "message" in a UIAlertView. I am using this UIAlertView as a geofence message. The geofence works if I just set the UIAlertView to a basic one where I enter in the message, yet I need it to call the message they wrote for another user to read. So far this setup only pops up the "Ok" button and nothing else once entered into a region.

func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
    showAlert(withTitle: name, message: message)
    //showAlert(withTitle: "Enter \(region.identifier)", message: "Geofence Message")
    print(state)
    print("region :\(region.identifier)")

}

func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
    showAlert()
    //showAlert(withTitle: "Enter \(region.identifier)", message: "Geofence Message")
    print("DID ENTER REGION")
}

func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
    //showAlert(withTitle: "Exit \(region.identifier)", message: "Message Exit")
    //TODO: stop local sequence
    print("DID EXIT REGION")

}

func showAlert(withTitle title: String?, message: String?) {
    FIRDatabase.database().reference().child("Businesses").observeSingleEvent(of: .value, with: { snapshot in
        if let dictionary = snapshot.value as? [String: AnyObject] {
            self.name = dictionary["businessName"] as? String
            self.message = dictionary["geofenceMessage"] as? String
        }
        let alert = UIAlertController(title: self.name, message: self.message, preferredStyle: .alert)
        let action = UIAlertAction(title: "Ok", style: .cancel, handler: nil)
        alert.addAction(action)
        self.present(alert, animated: true, completion: nil)
    })
}

More Info

// Populate Map With Firebase Businesses
func loadPlaces(){
    if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
    FIRDatabase.database().reference().child("Businesses").observe(.value, with: { snapshot in
        self.locationData = snapshot.value as? NSDictionary
        if let data = self.locationData{
            for (key,obj) in data{
                let value = obj as? NSDictionary
                let locationValue = value as! [String: Any]
                let lat = Double(locationValue["businessLatitude"] as! String)
                let long = Double(locationValue["businessLongitude"] as! String)
                let businessTitle = String(locationValue["businessName"] as! String)
                let center = CLLocationCoordinate2D(latitude: lat!, longitude: long!)

                let radius = CLLocationDistance(500.0)
                let geoRegion = CLCircularRegion(center: center, radius: radius, identifier: businessTitle!)
                self.geofences.append(geoRegion)
                self.locationManager.startMonitoring(for: geoRegion)
                let overlay = MKCircle(center: center, radius: radius)
                self.mapView.add(overlay)
                geoRegion.notifyOnEntry = true
                geoRegion.notifyOnExit = true

                let annotation = MKPointAnnotation()
                annotation.coordinate = geoRegion.center
                annotation.title = businessTitle
                self.mapView.addAnnotation(annotation)

                self.nameKeyDict[(value?.value(forKey: "businessName") as? String)!] = key as? String
            }
        }
    })
    } else {
        print("No Bueno")
    }
}

Firebase Data Structure

FireBase Data Structure

KENdi
  • 7,576
  • 2
  • 16
  • 31
Lukas Bimba
  • 817
  • 14
  • 35

4 Answers4

0

This problem occurs because you are creating the alert before the name and title variables are set. Try changing the initialization of name and title to main queue. And use Firebase observeSingleEvent that has a completion block and create the alert inside of the completion to make sure the value fetching from firebase is complete.

Frankenstein
  • 15,732
  • 4
  • 22
  • 47
0

It is because the observer event is called asynchronously and your alertcontroller is called before the values have been fetched.

Use one function to retrieve your firebase values with a completion handler like so:

func retrieveGeofenceMessage(with region: CLRegion, completionHandler: @escaping (Bool) -> Void){
 FIRDatabase.database().reference().child("Businesses").child(region.identifier).observeSingleEvent(of: .value, with: { snapshot in
    if let dictionary = snapshot.value as? [String: AnyObject] {
        self.name = dictionary["businessName"] as? String
        self.message = dictionary["geofenceMessage"] as? String

        }
        completionHandler(true)
    })
}

Then from where you call the function you write:

func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
    retrieveGeofenceMessage(with: region){
            if $0 {
                // Create Alert controller here
                let alert = UIAlertController(title: self.name, message: self.message, preferredStyle: .alert)
                let action = UIAlertAction(title: "Ok", style: .cancel, handler: nil)
                alert.addAction(action)
                self.present(alert, animated: true, completion: nil)

            }

        }

        print("DID ENTER REGION")
}

When the observe event has been executed you are now notified and assured that you won't use any variables not yet set.

matiastofteby
  • 431
  • 4
  • 18
  • Not sure what I would put where you have "YourFirebaseClassObject" – Lukas Bimba Aug 17 '17 at 18:56
  • This is because I assume that you have created a class where all of your firebase-calls are made and you thus call the function from outside that class. If you call it within the class you merely have to right the function name or maybe "self." – matiastofteby Aug 17 '17 at 18:58
  • I just make my firebase calls using FIRDatabase.database().reference each time – Lukas Bimba Aug 17 '17 at 18:59
  • Alright, but you call the function from somewhere else than the definition. If the call is from within the same class as you have defined the function then erase "YourFirebaseClassObject" or write self. If from outside the class, write the class name. – matiastofteby Aug 17 '17 at 19:02
  • I see you call it from your location manager functions so just leave the "YourFirebaseClassObject" out and write the rest in the functions. – matiastofteby Aug 17 '17 at 19:03
  • I am having a tough time understanding this. I am not sure what I "class" or "function" I would have to replace "YourFirebaseClassObject" with. How would I call this Alert? Because I previously I put "showAlert()" where "didEnterRegion" is and that would call the alert. Not sure what I put at "didEnterRegion" – Lukas Bimba Aug 17 '17 at 19:09
  • I have updated the answer to show you how to call it in didEnterRegion – matiastofteby Aug 17 '17 at 19:12
  • Yes that makes perfect sense now, sorry I had a tough time imagining where it would be put – Lukas Bimba Aug 17 '17 at 19:14
  • No worries. Obviously now name and message has to properties of the class so they can be accessed from within didEnterRegion after setting them in the function. – matiastofteby Aug 17 '17 at 19:15
  • Please upvote and accept answer if the solution works for you. – matiastofteby Aug 17 '17 at 19:21
  • Just tested it, I still just get the "Ok" button. No title or message – Lukas Bimba Aug 17 '17 at 19:22
  • Have you stored the properties anywhere other than in the function itself? Otherwise, I think the error might lie in that you have to explicitly unwrap the values from your dictionary. So write "as!" instead of "as?". Optionals won't be displayed on screen. Also it looks like you're using an outdated version of Firebase. The call would now be "Database" instead of "FIRDatabase" – matiastofteby Aug 17 '17 at 19:26
  • I think the problem is I am not saying which child to get those string from. I just added "More Info" to the original question, showing how I populate my map with "locations" from firebase. How would I say that this specific region is this child to call those strings from? – Lukas Bimba Aug 17 '17 at 19:32
  • I just added a photo of my data structure – Lukas Bimba Aug 17 '17 at 19:36
  • How is a picture of UIDs going to help? If you mean to say that it is because you don't how to access your data because of them I suggest you read the documentation - and then your question seems to be completely off topic. When you actually access you values via a correct childpath you'll need your code structured the way shown above. – matiastofteby Aug 17 '17 at 19:39
  • I was just saying maybe thats the problem because the alert is just the "ok" button. – Lukas Bimba Aug 17 '17 at 19:41
  • That is most definitely also a problem. Go to the firebase documentation and read about how to access child paths with uid to begin with. – matiastofteby Aug 17 '17 at 19:42
  • I know how to access by UID but the uid changes with each region – Lukas Bimba Aug 17 '17 at 19:47
  • Then just add the region.identifier as a parameter to your retrieveGeofenceMessage-function and use it to access the correct path. You already have access to the identifier in your didEnterRegion delegate method. – matiastofteby Aug 17 '17 at 19:50
  • I have updated the answer to how I suppose it might look - not tested though. If this isn't the case you'll have to figure the rest out on your own. – matiastofteby Aug 17 '17 at 19:59
0

Try using this way hope it will help

    FIRDatabase.database().reference().child("Businesses").observeSingleEvent(of: .value, with: { snapshot in
        if let dictionary = snapshot.value as? [String: AnyObject] {
            self.name = dictionary["businessName"] as? String
            self.message = dictionary["geofenceMessage"] as? String
            self.alertPopup(name: self.name, message: self.message)
        }

    })

}

func alertPopup (name: NSString, message: NSString){
    let alert = UIAlertController(title: name as String, message: message as String, preferredStyle: .alert)
    let action = UIAlertAction(title: "Ok", style: .cancel, handler: nil)
    alert.addAction(action)
    self.present(alert, animated: true, completion: nil)
}
iOS Geek
  • 4,825
  • 1
  • 9
  • 30
0

Here is the answer for anyone interested, very simple query check to firebase. Instead of all the craziness other people posted here. Just remember to set your "region.identifier" to the same thing as the "queryOrderded" so Firebase pulls the correct "users" data. Then have ".childAdded" instead of ".value" in the firebase call to check the "users" in your Firebase Database instead of their values.

func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
    FIRDatabase.database().reference().child("Businesses").queryOrdered(byChild: "businessName").queryEqual(toValue: region.identifier).observe(.childAdded, with: { snapshot in
        if snapshot.exists() {
            if let dictionary = snapshot.value as? [String:Any] {
                self.name = dictionary["businessName"] as? String
                self.message = dictionary["geofenceMessage"] as? String
            }
            let alert = UIAlertController(title: self.name, message: self.message, preferredStyle: .alert)
            let action = UIAlertAction(title: "Ok", style: .cancel, handler: nil)
            alert.addAction(action)
            self.present(alert, animated: true, completion: nil)
        } else {
            print("Snap doesnt exist")
        }
    })
print("DID ENTER REGION")
}
Lukas Bimba
  • 817
  • 14
  • 35
  • Just because you do not understand the answers do not make them "crazy". It would suit you to be a bit humble about the people spending their freetime to help you. – matiastofteby Aug 21 '17 at 13:48