Search code examples
arraysswiftgoogle-cloud-firestoreswiftuimapkitannotation

How to return an array of annotations from a function with a snapshot listener?


I have a function which listens for realtime updates in Cloud Firestore:

func getAnnotations() {

    FirestoreReferenceManager.referenceForAnnotations().addSnapshotListener { (querySnapshot, err) in
        
        guard (querySnapshot?.documents) != nil
            else {
                print("No documents")
                return
            }

        let exampleAnnotation = MKPointAnnotation()
        exampleAnnotation.coordinate = CLLocationCoordinate2D(latitude: CLLocationDegrees(51.5074), longitude: CLLocationDegrees(0.1278))
        let exampleSecondAnnotation = MKPointAnnotation()
        exampleSecondAnnotation.coordinate = CLLocationCoordinate2D(latitude: CLLocationDegrees(51.5074), longitude: CLLocationDegrees(0.1278))
        
        var annotationArray = [exampleAnnotation, exampleSecondAnnotation]
        
        for document in querySnapshot!.documents {

            let lat = document.get("latitude") as! String
            let lon = document.get("longitude") as! String
            print(lat, lon)
                        
            let latitude =  Double(lat)
            let longitude = Double(lon)
            let truecoordinate : CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: latitude ?? 0, longitude: longitude ?? 0)

            let annotation = MKPointAnnotation()
            annotation.coordinate = truecoordinate
            annotationArray.append(annotation)
        }
        //return annotationArray
    }
}

Commented line leads to following error:

"Unexpected non-void return value in void function"

And I want to return annotationArray... Any solutions?


Solution

  • What you need is a completion handler since the task is asynchronous. You can return a Result object and get slightly more fancy with it or just return nil when there is an error and an empty array when there are no results. Both will get the job done just fine. You should also avoid force unwrapping document fields because it could crash the app entirely; always unwrap optionals safely.

    func getAnnotations(completion: @escaping (_ annotations: [MKPointAnnotation]?) -> Void) {
        FirestoreReferenceManager.referenceForAnnotations().addSnapshotListener { (querySnapshot, err) in
            guard let snapshot = querySnapshot else {
                if let err = err {
                    print(err)
                }
                
                completion(nil) // return nil if error
                return
            }
            guard !snapshot.isEmpty else {
                completion([]) // return empty if no documents
                return
            }
            var annotations = [MKPointAnnotation]()
            
            for doc in snapshot.documents {
                if let lat = doc.get("latitude") as? String,
                   let lon = doc.get("longitude") as? String,
                   let latitude =  Double(lat),
                   let longitude = Double(lon) {
                    let coord = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
                    let annotation = MKPointAnnotation()
                    
                    annotation.coordinate = coord
                    annotations.append(annotation)
                }
            }
            
            completion(annotations) // return array
        }
    }
    

    Usage

    getAnnotations { (annotations) in
        if let annotations = annotations {
            // add to map
        } else {
            // handle error
        }
    }
    

    That said, I would store the coordinates in Firestore as a GeoPoint and simply translate them into CLLocationCoordinate2D instead of storing them as strings and translating them into doubles and then into coordinates. This is unclean, unnecessary, and more prone to errors.