Search code examples
iosswiftmkmapviewnsoperationmarkerclusterer

how can I synchronize a call to my webservice and displaying the data on the map (with marker clustering) in Swift?


I'm writing an app that displays lots of points on the map. I can fetch all of them as a json and put it on the map, but then it's just too many points, so I thought about implementing marker cluster. Since I'm working in Swift, I've decided to use this library: https://github.com/ribl/FBAnnotationClusteringSwift

To avoid heavy data usage on my map I've created a backend webservice that gets as a parameters current user location and visible radius. As an answer it returns a JSON with gps coordinates of points of interest.

Since I'm using AlamoFire and SwiftyJSON, I created a method to fetch records from my webservice and as you can see below, it uses the current center of the map (lat and lon) and also visible radius:

func fetchRecords(radius: Double, lat: Double, lon: Double){
        Alamofire.request(.GET, "http://mywebservice/records", parameters: ["long": lon, "lat": lat, "radius": radius ])
            .responseJSON { response in

                switch response.result {
                case .Success:
                    self.array.removeAll()
                    if let jsonData = response.result.value as? [[String: AnyObject]] {
                        for requestJSON in jsonData {
                            if let request = SingleRecord.fromJSON(JSON(requestJSON)){

                                let pinOne = FBAnnotation()
                                pinOne.coordinate = CLLocationCoordinate2D(latitude: request.latitude, longitude: request.longitude)
                                self.array.append(pinOne)

                            }
                        }
                    }

                    self.clusteringManager.addAnnotations(self.array)

                case .Failure(let error):
                    print("SWITCH ERROR")
                    print(error)
                }

        }
    }

I'm fetching records for the current position and I'm creating a single FBAnnotation() for each record. Also, I'm appending all points to the array `self.array'. So far so good.

I want to fetch always actual data from the server, so I've decided to implement method regionDidChangeAnimated:

func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {

    self.center = mapView.centerCoordinate.latitude.description
    self.radius = self.getRadius(mapView)


    NSOperationQueue().addOperationWithBlock({

        self.fetchRecords(self.radius, lat: self.mapView.centerCoordinate.latitude, lon: self.mapView.centerCoordinate.longitude)

        let mapBoundsWidth = Double(self.mapView.bounds.size.width)

        let mapRectWidth:Double = self.mapView.visibleMapRect.size.width

        let scale:Double = mapBoundsWidth / mapRectWidth
        let annotationArray = self.clusteringManager.clusteredAnnotationsWithinMapRect(self.mapView.visibleMapRect, withZoomScale:scale)

        self.clusteringManager.displayAnnotations(annotationArray, onMapView:self.mapView)

    })

}

What I did above is to - first - fetch all current records for the given position and then use the prepared code from FBAnnotationClusteringSwift.

But it doesn't work and I suspect it's because of the threading issue, maybe the array is not yet filled, or something (?), but the result is as follows:

when I open the map I see:

enter image description here

but when I slightly move the map, suddenly the markers increment:

enter image description here

and with every move happens the same:

enter image description here

and so on. The same happens when I zoom in the map, even when there's one marker cluster left in the middle of the screen - with every zoom step it increases its number instead of changing to a single pin. I tried to remove all the elements from the array every time after calling fetchRecords, but it also didn't help.

Does anyone know what might be the issue here? I know it's a rare topic, but maybe someone could give me any hint with that, I will really appreciate it. Thanks!


Solution

  • Add a completion handler in your fetch function

    func fetchRecords(radius: Double, lat: Double, lon: Double, completionHandler: (() -> Void)?){
        Alamofire.request(.GET, "http://mywebservice/records", parameters: ["long": lon, "lat": lat, "radius": radius ])
            .responseJSON { response in
    
                switch response.result {
                case .Success:
                    self.array.removeAll()
                    if let jsonData = response.result.value as? [[String: AnyObject]] {
                        for requestJSON in jsonData {
                            if let request = SingleRecord.fromJSON(JSON(requestJSON)){
    
                                let pinOne = FBAnnotation()
                                pinOne.coordinate = CLLocationCoordinate2D(latitude: request.latitude, longitude: request.longitude)
                                self.array.append(pinOne)
    
                            }
                        }
                    }
                    //TODO: -Clean your data before adding new things
    
                    self.clusteringManager.addAnnotations(self.array)
                    //call the closure after everything is done
                    completionHandler()
    
                case .Failure(let error):
                    print("SWITCH ERROR")
                    print(error)
                }
    
        }
    }
    

    And use it in the main function

    self.fetchRecords(self.radius, lat: self.mapView.centerCoordinate.latitude, lon: self.mapView.centerCoordinate.longitude) { [weak self] in
    
        let mapBoundsWidth = Double(self?.mapView.bounds.size.width)
    
        let mapRectWidth:Double = self?.mapView.visibleMapRect.size.width
    
        let scale:Double = mapBoundsWidth / mapRectWidth
        let annotationArray = self?.clusteringManager.clusteredAnnotationsWithinMapRect(self.mapView.visibleMapRect, withZoomScale:scale)
    
        self?.clusteringManager.displayAnnotations(annotationArray, onMapView:self?.mapView)
    }
    

    This way you will make sure that the self?.clusteringManager.displayAnnotations is done after your fetch