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:
but when I slightly move the map, suddenly the markers increment:
and with every move happens the same:
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!
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