Code Monkey home page Code Monkey logo

cluster's Introduction

Cluster

Build Status Carthage Compatible Language Version License Platform

Cluster is an easy map annotation clustering library. This repository uses an efficient method (QuadTree) to aggregate pins into a cluster.

Demo Screenshots

Features

  • Adding/Removing Annotations
  • Clustering Annotations
  • Multiple Managers
  • Dynamic Cluster Disabling
  • Custom Cell Size
  • Custom Annotation Views
  • Animation Support
  • Documentation

Requirements

  • iOS 8.0+
  • Xcode 9.0+
  • Swift 5 (Cluster 3.x), Swift 4 (Cluster 2.x), Swift 3 (Cluster 1.x)

Demo

The Example is a great place to get started. It demonstrates how to:

  • integrate the library
  • add/remove annotations
  • reload annotations
  • configure the annotation view
  • configure the manager

$ pod try Cluster

Installation

Cluster is available via CocoaPods and Carthage.

CocoaPods

To install Cluster with CocoaPods, add this to your Podfile:

pod "Cluster"

Carthage

To install Cluster with Carthage, add this to your Cartfile:

github "efremidze/Cluster"

Usage

The Basics

The ClusterManager class generates, manages and displays annotation clusters.

let clusterManager = ClusterManager()

Adding an Annotation

Create an object that conforms to the MKAnnotation protocol, or extend an existing one. Next, add the annotation object to an instance of ClusterManager with add(annotation:).

let annotation = Annotation(coordinate: CLLocationCoordinate2D(latitude: 21.283921, longitude: -157.831661))
manager.add(annotation)

Configuring the Annotation View

Implement the map viewโ€™s mapView(_:viewFor:) delegate method to configure the annotation view. Return an instance of MKAnnotationView to display as a visual representation of the annotations.

To display clusters, return an instance of ClusterAnnotationView.

extension ViewController: MKMapViewDelegate {
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        if let annotation = annotation as? ClusterAnnotation {
            return CountClusterAnnotationView(annotation: annotation, reuseIdentifier: "cluster")
        } else {
            return MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
        }
    }
}

For performance reasons, you should generally reuse MKAnnotationView objects in your map views. See the Example to learn more.

Customizing the Appearance

The ClusterAnnotationView class exposes a countLabel property. You can subclass ClusterAnnotationView to provide custom behavior as needed. Here's an example of subclassing the ClusterAnnotationView and customizing the layer borderColor.

class CountClusterAnnotationView: ClusterAnnotationView {
    override func configure() {
        super.configure()

        self.layer.cornerRadius = self.frame.width / 2
        self.layer.masksToBounds = true
        self.layer.borderColor = UIColor.white.cgColor
        self.layer.borderWidth = 1.5
    }
}

See the AnnotationView to learn more.

Annotation Styling

You can customize the appearance of the StyledClusterAnnotationView by setting the style property of the annotation.

let annotation = Annotation(coordinate: CLLocationCoordinate2D(latitude: 21.283921, longitude: -157.831661))
annotation.style = .color(color, radius: 25)
manager.add(annotation)

Several styles are available in the ClusterAnnotationStyle enum:

  • color(UIColor, radius: CGFloat) - Displays the annotations as a circle.
  • image(UIImage?) - Displays the annotation as an image.

Once you have added the annotation, you need to return an instance of the StyledClusterAnnotationView to display the styled annotation.

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    if let annotation = annotation as? ClusterAnnotation {
        return StyledClusterAnnotationView(annotation: annotation, reuseIdentifier: identifier, style: style)
    }
}

Removing Annotations

To remove annotations, you can call remove(annotation:). However the annotations will still display until you call reload().

manager.remove(annotation)

In the case that shouldRemoveInvisibleAnnotations is set to false, annotations that have been removed may still appear on map until calling reload() on visible region.

Reloading Annotations

Implement the map viewโ€™s mapView(_:regionDidChangeAnimated:) delegate method to reload the ClusterManager when the region changes.

func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    clusterManager.reload(mapView: mapView) { finished in
        // handle completion
    }
}

You should call reload() anytime you add or remove annotations.

Configuring the Manager

The ClusterManager class exposes several properties to configure clustering:

var zoomLevel: Double // The current zoom level of the visible map region.
var maxZoomLevel: Double // The maximum zoom level before disabling clustering.
var minCountForClustering: Int // The minimum number of annotations for a cluster. The default is `2`.
var shouldRemoveInvisibleAnnotations: Bool // Whether to remove invisible annotations. The default is `true`.
var shouldDistributeAnnotationsOnSameCoordinate: Bool // Whether to arrange annotations in a circle if they have the same coordinate. The default is `true`.
var distanceFromContestedLocation: Double // The distance in meters from contested location when the annotations have the same coordinate. The default is `3`.
var clusterPosition: ClusterPosition // The position of the cluster annotation. The default is `.nearCenter`.

ClusterManagerDelegate

The ClusterManagerDelegate protocol provides a number of functions to manage clustering and configure cells.

// The size of each cell on the grid at a given zoom level.
func cellSize(for zoomLevel: Double) -> Double? { ... }

// Whether to cluster the given annotation.
func shouldClusterAnnotation(_ annotation: MKAnnotation) -> Bool { ... }

Communication

  • If you found a bug, open an issue.
  • If you have a feature request, open an issue.
  • If you want to contribute, submit a pull request.

Mentions

Credits

License

Cluster is available under the MIT license. See the LICENSE file for more info.

cluster's People

Contributors

andris-zalitis avatar efremidze avatar eleev avatar funkenstrahlen avatar gallettube avatar grifas avatar nikitabelosludtcev avatar the-freshlord avatar trentfitzgibbon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cluster's Issues

Erros Cluster.swift

Hello There,

After install the Cluster pod on my project and without make any further changes. The application got 3 erros.

These 2 :

visibleAnnotations.subtract(Set(toRemove as! [NSObject]))
visibleAnnotations.formUnion(Set(toAdd as! [NSObject]))

With : Type 'NSObject' does not conform to protocol 'MKAnnotation'

And these 1:

let after = Set(clusteredAnnotations as! [NSObject])

With : Type 'NSObject' does not conform to protocol 'MKAnnotation'

It's normal by default the pop to have this erros?

MKQuadTrie crash

Hello again!

I had a (random) crash today saying:

'NSInvalidArgumentException', reason: '*** -[__NSSetM addObject:]: object cannot be nil'

Here is the entire stack trace:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSSetM addObject:]: object cannot be nil'
*** First throw call stack:
(
	0   CoreFoundation                      0x0000000105ed4b0b __exceptionPreprocess + 171
	1   libobjc.A.dylib                     0x0000000105939141 objc_exception_throw + 48
	2   CoreFoundation                      0x0000000105e29e3f -[__NSSetM addObject:] + 783
	3   MapKit                              0x000000010514b08b __30-[MKQuadTrie _itemsInMapRect:]_block_invoke + 634
	4   MapKit                              0x000000010514a89e _Z17__depthFirstApplyP14MKQuadTrieNodeU13block_pointerF28MKQuadTrieTraversalDirectiveS0_E + 94
	5   MapKit                              0x000000010514999c -[MKQuadTrie _itemsInMapRect:] + 343
	6   MapKit                              0x0000000105149b0c -[MKQuadTrie itemsInMapRect:] + 304
	7   MapKit                              0x0000000105149f7d -[MKQuadTrie allItems] + 104
	8   MapKit                              0x0000000105132300 -[MKAnnotationManager annotations] + 73
	9   MapKit                              0x000000010506b6c7 -[MKMapView annotations] + 39
	10  Cluster                             0x0000000104e3b6cf _TFC7Cluster14ClusterManager20clusteredAnnotationsfTCSo9MKMapView14visibleMapRectVSC9MKMapRect9operationCSo9Operation_T5toAddGSaPSo12MKAnnotation__8toRemoveGSaPS4____ + 8079
	11  Cluster                             0x0000000104e391d4 _TFFC7Cluster14ClusterManager6reloadFTCSo9MKMapView14visibleMapRectVSC9MKMapRect_T_U_FT_T_ + 420
	12  Cluster                             0x0000000104e39588 _TPA__TFFC7Cluster14ClusterManager6reloadFTCSo9MKMapView14visibleMapRectVSC9MKMapRect_T_U_FT_T_ + 136
	13  Cluster                             0x0000000104e396e7 _TTRXFo___XFdCb___ + 39
	14  Foundation                          0x0000000105443237 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 7
	15  Foundation                          0x0000000105442f3b -[NSBlockOperation main] + 101
	16  Foundation                          0x00000001054416f7 -[__NSOperationInternal _start:] + 627
	17  Foundation                          0x000000010543d47c __NSOQSchedule_f + 198
	18  libdispatch.dylib                   0x000000010a29e05c _dispatch_client_callout + 8
	19  libdispatch.dylib                   0x000000010a27c94f _dispatch_queue_serial_drain + 221
	20  libdispatch.dylib                   0x000000010a27d669 _dispatch_queue_invoke + 1084
	21  libdispatch.dylib                   0x000000010a27fec4 _dispatch_root_queue_drain + 634
	22  libdispatch.dylib                   0x000000010a27fbef _dispatch_worker_thread3 + 123
	23  libsystem_pthread.dylib             0x000000010a630616 _pthread_wqthread + 1299
	24  libsystem_pthread.dylib             0x000000010a6300f1 start_wqthread + 13
)
libc++abi.dylib: terminating with uncaught exception of type NSException

Here is the source file:

//
//  MapController.swift
//

import Foundation
import UIKit
import MapKit
import Cluster

// MARK: - MapController class

class MapController: UIViewController {

    // MARK: Properties

    fileprivate let clusterManager = ClusterManager()

    // MARK: Outlets

    @IBOutlet weak var mapView: MKMapView!

    // MARK: Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()

        // Initial region
        self.mapView.setRegion(/* random things */, animated: false)
    }
}

// MARK: - Partners displayable

extension MapController: PartnersDisplayable {

    func update(partners: [Partner]) {

        // Remove partners without locations
        let filteredPartners = partners.filter { $0.location != nil }

        // Create annotations
        let annotations = filteredPartners.map {
            return PartnerAnnotation(partner: $0)
        }

        // Add annotations to the cluster manager
        self.clusterManager.removeAll()
        self.clusterManager.add(annotations)
        self.clusterManager.reload(self.mapView, visibleMapRect: self.mapView.visibleMapRect)
    }
}

// MARK: - Map view delegate

extension MapController: MKMapViewDelegate {

    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

        // Show a cluster pin or a normal pin
        if let annotation = annotation as? ClusterAnnotation {

            let reuseIdentifier = "PartnerClusterAnnotationView"
            let view = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
                ?? ClusterAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier, type: .color(.mainTintNormal, radius: 30))

            view.annotation = annotation

            return view

        } else if let annotation = annotation as? PartnerAnnotation {

            let view = UINib.partnerAnnotationView.instantiate(withOwner: self, options: nil).first as! PartnerAnnotationView
            view.annotation = annotation
            view.configure(with: annotation.partner)

            return view

        } else {
            return nil
        }
    }

    func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
        /* random things */
    }

    func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
        /* random things */
    }

    func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
        self.clusterManager.reload(self.mapView, visibleMapRect: self.mapView.visibleMapRect)
    }
}

// MARK: - Navigation

extension MapController {

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        /* random things */
    }
}

exception

Do you have any idea on what happens?

Thanks you very much!

Thomas

Performing action when individual annotation is clicked

He

New Issue Checklist

Issue Description

Hello!

Hope all is well, I have a really quick question. I've looked through the previously posted issues and don't seem to see anyone else asking. How do you perform an action whenever a rightCalloutAccessoryView button is clicked or whenever an individual annotation is clicked. I'm attempting to use this function to test my button being fired but no luck

func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
print("pressed")
}

Environment

  • iOS Version: [IOS 11]
  • Device(s): [iPhone 6]

Clustor pin is not removing on zoom when pin have Exact Same location.

Hi,

I am using latest cluster. I am Facing issue with exact same location pins.
Here we have requirements for to show annotations for the same locations. and the locations will be the exact same. when i tap on cluster pin it zooms in and not removing cluster pin.

maplocissue

Please Help me ASAP.

Thank you.

Cluster Annotation wrongly Removed and re-Added

Hi!

What a great repo!

I strumbled on something that may be a bug.

When moving the mapView very slightly, the ClusterManager removes some ClusterAnnotation and re-add them right away. In does not seems to happen with Annotations.

Example of logs:
Deleting <Cluster.ClusterAnnotation: 0x171884dd0>
Deleting <Cluster.ClusterAnnotation: 0x171887940>
Deleting <Cluster.ClusterAnnotation: 0x175c90770>
Deleting <Cluster.ClusterAnnotation: 0x171892ca0>
Adding <Cluster.ClusterAnnotation: 0x171884dd0>
Adding <Cluster.ClusterAnnotation: 0x171887940>
Adding <Cluster.ClusterAnnotation: 0x175c90770>
Adding <Cluster.ClusterAnnotation: 0x171892ca0>

It's not very bad but if you have some processing done in the "viewFor" method of the mapView this is annoying.

I tried to play with the Set but could not figure out what was wrong. It seems that for some reason some ClusterAnnotations in before and after are considered unequal, whereas they are equal.

Any idea ?

Any help will be greatly appreciated.

Access custom annotations properties

Hi!
I have a custom annotation class named MyAnnotations, i made an array:
var newArray:[MyAnnotation] = []
then i add annotations to that array:
self.newArray.append(annotation)
and then self.manager.add(self.newArray)

now i can see annotations and clusters on map but i can't access properties of MyAnnotation class, if i make: let MyAnnotations = view.annotation as! MyAnnotations i get:
Could not cast value of type 'Cluster.ClusterAnnotation' (0x7feca9419090) to 'app.MyAnnotations'
I really need to access all properties of that class, can you help me?

ClusterAnnotationType Image with count label

When I choose ClusterAnnotationType.color clusterview shows annotation count but When I choose ClusterAnnotationType.image annotatation count is not shows.

There is a way show count without change library codes?

Thanks

Xcode 9 runtime issue

When I run my project I get this runtime issue: UIView.bounds must be used from main thread only

This is in Cluster.swift line 118

Thanks

Roughly on 3000+ locations while scrolling the map (w/o zooming) cluster recalculation freezes the map for a couple of seconds (fully unresponsive).

iOS 10/11
Any device

  • Roughly on 3000+ locations while scrolling the map (w/o zooming) cluster recalculation freezes the map for a couple of seconds (fully unresponsive). The older the processor the longer the freeze.

  • Even slight scrolling causes this freeze.

  • It's worth also to note that for the same map, with the same number of locations there is no such a freeze when zooming out/in, although recalculation should be similar.

screenshot at apr 02 00-46-50

ClusteringManager crashing

I'm having a problem with my program crashing while using clustering manager. I'm trying to build an app that will display the status of houses using coloured icons.
screen shot 2017-06-29 at 3 39 38 pm

It gives me an index out of range error when the map reloads, however the error only occurs occasionally. I can't tell from the stack trace where the error is occuring, since it takes me to a line Cluster.swift. Does anyone have any idea on what sort of thing could be causing the error? I have no idea what array could be out of range to cause this error.

screen shot 2017-06-29 at 3 55 35 pm

I'm thought the error is coming from the function below (as it sets the MKAnnotationView) but I swapped it for the function in the example project and it still crashes.

//Display annotations and pins for the map
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

    var reuseId = ""
    let orangeColor = UIColor(red: 255/255, green: 149/255, blue: 0/255, alpha: 1)
    let redColor = UIColor(red: 255/255, green: 0/255, blue: 0/255, alpha: 1)
    let greenColor = UIColor(red: 6/255, green: 152/255, blue: 6/255, alpha: 1)
    
    if let annotation = annotation as? ClusterAnnotation {
        let identifier = "Cluster"
        var view = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
        if view == nil {
            if let annotation = annotation.annotations.first as? Annotation, let type = annotation.type {
                view = BorderedClusterAnnotationView(annotation: annotation, reuseIdentifier: identifier, type: type, borderColor: .white)
            } else {
                let allAnnos = annotation.annotations
                if (!allAnnos.isEmpty && !addressStatus.isEmpty){
                    for anno in allAnnos {
                    if (addressStatus[anno.title!!] == "Offline") {
                        view = ClusterAnnotationView(annotation: annotation, reuseIdentifier: identifier, type: .color(redColor, radius: 25))

                        return view
                        
                    } else if (addressStatus[anno.title!!] == "Error"){
                        view = ClusterAnnotationView(annotation: annotation, reuseIdentifier: identifier, type: .color(orangeColor, radius: 25))

                        return view
                        
                    }
                    }
                }
                view = ClusterAnnotationView(annotation: annotation, reuseIdentifier: identifier, type: .color(greenColor, radius: 25))
            }
        } else {
            let allAnnos = annotation.annotations
            if (!allAnnos.isEmpty && !addressStatus.isEmpty) {
                for anno in allAnnos {
                    if (addressStatus[anno.title!!] == "Offline") {

                        view = ClusterAnnotationView(annotation: annotation, reuseIdentifier: identifier, type: .color(redColor, radius: 25))

                        return view
                    
                    } else if (addressStatus[anno.title!!] == "Error"){

                        view = ClusterAnnotationView(annotation: annotation, reuseIdentifier: identifier, type: .color(orangeColor, radius: 25))

                        return view
                    
                    }
                }
                view = ClusterAnnotationView(annotation: annotation, reuseIdentifier: identifier, type: .color(greenColor, radius: 25))
            }
        }

        return view
        
    } else {
        reuseId = "Pin"
        var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId)
            pinView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
            pinView?.canShowCallout = true
            let address = annotation.title
        
            if (addressStatus[address!!] == "OK") {
                pinView?.image = UIImage(named:"House Icon Green.png")!
            } else if (addressStatus[address!!] == nil) {
                pinView?.image = UIImage(named:"House Icon Black.png")!
            } else if (addressStatus[address!!] == "Error") {
                pinView?.image = UIImage(named:"House Icon Orange.png")!
            } else if (addressStatus[address!!] == "Offline") {
                pinView?.image = UIImage(named:"House Icon Red.png")!
            }

        
            pinView?.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
        
        return pinView
    }
}

Any help would be greatly appreciated thank you!

on .reload method call map view region changed.

Hi,
It's great lib and i would like to thank you for making it easier.

Here i am facing one issue regarding the selecting annotation.
When i select annotation the map is changed it's region. you can see in the below gif.

There are two issues

  1. as you can see thay the zoom level of the map is so that both end (left and right) pin is cut. i tried a lot but when ever i reload the cluster it always gives me this result. You can see it in the GIF after stopped progressview.

  2. when i tap on diff diff annotations i select the annotation and reload cluster method is called from regionDidChangeAnimated as we need to show that annotation. but this time the map is getting don and down the annotations are changing positions on map and the map moves :(.

Please help me.

Thank you
test123

How to show Cluster for MKPolyline with geojson data View in iOS Swift?

I am loading Mapview with geojson data. It contains 7000 data in that geojson file. Even I loaded that data into Mapview. Its loaded fine, but, the problem is mapview getting freezing/stucking for few seconds while zoomin/zoomou So, its getting very bad user experience.

Even, I loaded the data in background thread, still mapview getting freeze.

After I read few forums, we can load clusters to avoid this kind freeze UI issue for mapview, but, I did not found for geojson data loading with clusters and for MKpolylines. I am showing in my mapview Polylines and Annoations by loading 2 geojson files to MKMapview.

Anyone can suggest me, how to show clusters for MKPolylines and Annotations by loading with geojson files.

Pins grouping issue

Hello again!

As I am adding a lot of pins on my map, I am facing an issue with grouping. The grouping algorithm seems not to be predictive. If I zoom/unzoom a little bit, sometime a pin will be part of group 1, and sometime on group 2. This can lead to weird things (see attached video).

Is it easy to fix or not? :)

cluster-issue.mp4.zip

Remove Multithreading

Threading should be handled by the user instead of this library. It would remove the need for completion blocks. Could add an option for asynchronous operation.

Selecting ClusterAnnotation

Hi,

I really like your solution as other's I've been using were written in Objective-C and I've run into some bridging issues. But I've run into issue here as well.

Common pattern I want to use is that when user selects cluster, app zooms in to make all annotations in that cluster visible. So I've implemented delegate method to the example to try that.

func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
    guard let annotation = view.annotation else { return }
        
    if let cluster = annotation as? ClusterAnnotation {
        mapView.showAnnotations(cluster.annotations, animated: true)
    }
}

The behavior I get doesn't look nice. When the cluster is tapped, pins are added to map and when mapView(regionDidChangeAnimated:) is called, tapped cluster is removed and if some of the newly added pins are clusters they are transformed to reflect that. Would it be possible to do that in a better way?

Thanks

Totally wrong cluster count

Hey! I have tested the project using 2 pins only. If you zoom out a little, it shows cluster count 4 if it has to be one. If something at all

Cluster views change their location constantly when zooming (even very slightly) on low zoom

Issue Description

(1) Cluster views change their location constantly when zooming (even very slightly) on low zoom, (2) they move far from the actual pins locations,
(3) occasionally on very low zoom pins pop up.

All this somehow linked to this method manager.shouldCenterAlignClusters = true

Screenshots

screenshot at nov 23 15-23-57

Environment

  • iOS Version: IOS 11, IOS 10
  • Device(s): iPhone 6s+, iPhone 5

Remove a single annotation

It would be very useful to have an additional API method for removing a single annotation. Right now there is a single method called removeAll(), but I suggest to add a new method with the following signature: remove(_ annotation: MKAnnotation).

It would complement the existing API similar to the one used in MKMapView (mapView.remove(annotation)).

Thank you!

Cluster pin changes it's place when drag the map.

hi,

having another issue as below.

When i drag the map i the cluster pin changes it's location. I did print toAdd and toRemove array of annotations with coordinates and the log is attached below. When i drag the map "regionDidChangeAnimated" method called and here we added below line :

clusterManager.reload(MapView, visibleMapRect: MapView.visibleMapRect)

and in the reload method i did add below things.

open func reload(_ mapView: MKMapView, visibleMapRect: MKMapRect) {
        
        let zoomScale = ZoomScale(mapView.bounds.width) / visibleMapRect.size.width
        let (toAdd, toRemove) = clusteredAnnotations(mapView, zoomScale: zoomScale, visibleMapRect: visibleMapRect)
        
        for annoadd in toAdd {
            print("Location of toAdd Annotations  : \(annoadd.coordinate)")
        }
        for annorem in toRemove {
            print("Location of toRemove Annotations  : \(annorem.coordinate)")
        }
        mapView.removeAnnotations(toRemove)
        mapView.addAnnotations(toAdd)
        visibleAnnotations.subtract(toRemove)
        visibleAnnotations.add(toAdd)
    }

The issue is when i drag or scroll the map the pin changes its Coordinate. please look into GIF.

Log :

NOTE : We are showing annotations not pin on the map.

Location of toAdd Annotations : CLLocationCoordinate2D(latitude: 41.07258800000001, longitude: -81.508352789999989)
Location of toAdd Annotations : CLLocationCoordinate2D(latitude: 41.073535866666674, longitude: -81.506221999999994)
Location of toAdd Annotations : CLLocationCoordinate2D(latitude: 41.072601486018961, longitude: -81.508352789999989)
Location of toAdd Annotations : CLLocationCoordinate2D(latitude: 41.073549352685632, longitude: -81.506221999999994)

Location of toRemove Annotations : CLLocationCoordinate2D(latitude: 41.07258800000001, longitude: -81.508352789999989)
Location of toRemove Annotations : CLLocationCoordinate2D(latitude: 41.073535866666674, longitude: -81.506221999999994)

Location of toAdd Annotations : CLLocationCoordinate2D(latitude: 41.072614972037925, longitude: -81.508352789999989)
Location of toAdd Annotations : CLLocationCoordinate2D(latitude: 41.073562838704596, longitude: -81.506221999999994)

Location of toRemove Annotations : CLLocationCoordinate2D(latitude: 41.072601486018961, longitude: -81.508352789999989)
Location of toRemove Annotations : CLLocationCoordinate2D(latitude: 41.073549352685632, longitude: -81.506221999999994)

Location of toAdd Annotations : CLLocationCoordinate2D(latitude: 41.072628458056883, longitude: -81.508352789999989)
Location of toAdd Annotations : CLLocationCoordinate2D(latitude: 41.073576324723554, longitude: -81.506221999999994)

Location of toRemove Annotations : CLLocationCoordinate2D(latitude: 41.072614972037925, longitude: -81.508352789999989)
Location of toRemove Annotations : CLLocationCoordinate2D(latitude: 41.073562838704596, longitude: -81.506221999999994)

Location of toAdd Annotations : CLLocationCoordinate2D(latitude: 41.072641944075848, longitude: -81.508352789999989)
Location of toAdd Annotations : CLLocationCoordinate2D(latitude: 41.073589810742519, longitude: -81.506221999999994)

Location of toRemove Annotations : CLLocationCoordinate2D(latitude: 41.072628458056883, longitude: -81.508352789999989)
Location of toRemove Annotations : CLLocationCoordinate2D(latitude: 41.073576324723554, longitude: -81.506221999999994)

After region change

  1. after adding all pins (cluster pins) on the map and scroll the map
    UIView.animate
    Location of toAdd Annotations : CLLocationCoordinate2D(latitude: 41.072655430094805, longitude: -81.508352789999989)
    Location of toAdd Annotations : CLLocationCoordinate2D(latitude: 41.073603296761476, longitude: -81.506221999999994)
    Location of toRemove Annotations : CLLocationCoordinate2D(latitude: 41.072641944075848, longitude: -81.508352789999989)
    Location of toRemove Annotations : CLLocationCoordinate2D(latitude: 41.073589810742519, longitude: -81.506221999999994)

  2. after adding all pins (cluster pins) on the map and scroll the map
    UIView.animate
    Location of toAdd Annotations : CLLocationCoordinate2D(latitude: 41.072668916113777, longitude: -81.508352789999989)
    Location of toAdd Annotations : CLLocationCoordinate2D(latitude: 41.073616782780441, longitude: -81.506221999999994)
    Location of toRemove Annotations : CLLocationCoordinate2D(latitude: 41.072655430094805, longitude: -81.508352789999989)
    Location of toRemove Annotations : CLLocationCoordinate2D(latitude: 41.073603296761476, longitude: -81.506221999999994)

Kindly help me Please.

Thank you.

pin changes

Question/potential bug: Any reason for allowing duplicate annotations in the ClusterManager?

First off, thanks for getting this project started! Loving the simplicity and Swifty-ness and the simplicity so far, a fresh change of pace compared to CCH and other clustering libraries out there.

A quick question: I'm finding myself needing to wrap all my ClusterManager.add calls in a helper function that detects duplicates. Right now, it's possible to add the same instance to the ClusterManager multiple times (frustrating as it creates a permanent 'cluster' at that location).

Was there a reason for that? It's a little frustrating from a user's perspective, but curious to hear your thoughts.

(Happy to submit a PR for this, pending the response)

Work wrong

Make a circle of annotations like this image:
archivo 13-10-17 11 42 19

Support for multiple Cluster Managers

Hello,

Kudos for the great work on the library so far :)

I faced an issue when I tried to group pins based on a given type that I have.
It would be really convenient if I could configure multiple Cluster Manager instances. I've seen that feature in some older libs.

Do you think that would be possible to achieve in your library?

Thank you,
Granit

Exact Same Location.

Hey guys and very nice work! Congrats to all of you ๐Ÿ‘
Although I have one question.

I'm running one app about Events.
And some Venues have multiple Events in the exact same location (lat, lon) in the map.
Do we have a workaround about it?
Cause if i zoom in this location spot, the cluster doesn't break. And this is normal.
Thanks a lot !

annotation image

I guess there is no way to add images to annotations. I think its gonna be awesome feature for this project.

Incorrect annotation/clusterAnnotation for zoom level

Same issue in the FB library as well. As I slowly zoom out, my pins pop in and out of a single cluster. I can be entirely zoomed out and have two pins overlapping. I learned that this does not happen on particular zoom levels (2-20), but particular fractions of a zoom level.. if that makes any sense.

I can't see why it would be returning the wrong annotation type.

Selecting an annotation

Hi, this might have been raised before but not too sure.

I have a manager which has annotations, obviously some are clustered.
I want to select an annotation programmatically but if it is in a cluster, the view of the annotation is nil and therefore cannot be selected.
How would I select an annotation (i.e. move to the cluster, open the cluster and select the annotation that was inside it)?
I've tried moving to the location where the cluster should be without animation, but it can't be selected somehow.

Any ideas?

Custom colors

Can you parametrize border and text color, adding paramters to set the colors instead of white?

label.textColor = customColor // label.textColor = .white
layer.borderColor = customColor // UIColor.white.cgColor

Our background for example is white and I modified in my clash but if you allow to set them as we wish in the library it's better.

I want to add that the way to change the color of text:

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    var view = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
    if view == nil {
        view = ClusterAnnotationView(annotation: annotation, reuseIdentifier: identifier, type: type)
       (view as! ClusterAnnotationView).countLabel.textColor = UIColor.black
       (view as! ClusterAnnotationView).layer.borderColor = UIColor.black.cgColor
    } else {
        view?.annotation = annotation
    }
    return view
}

removing the line below in Annotation the layer.borderColor can be set from outside
layer.borderColor = UIColor.white.cgColorlayer.borderColor = UIColor.white.cgColor
Thanks

Strange expand/ shrinking animation of an image cluster pin.

iOS 11, Cluster v. 2.1.3

We are using png image for cluster pin. This png image is scaled depending on the size of the clusters.
So the more places clustered in the cluster, the bigger the scaling factor is. In ios 10 it works well, but in iOS 11 there is a strange expand/ shrinking animation.

Any idea where this animation is coming from and how can we get rid of it?

Below is the code that we are using to scale the image and the gif.

untitled-3

class RetailerClusterAnnotationView: ClusterAnnotationView {
    
    
    var pinTextColor: UIColor = .white
    
    required init(annotation: MKAnnotation?, reuseIdentifier: String?, style: ClusterAnnotationStyle, pinTextColor: UIColor) {
        self.pinTextColor = pinTextColor
        
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier, style: style)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func configure(with type: ClusterAnnotationStyle) {
        //  super.configure(with:type)
        guard let annotation = annotation as? ClusterAnnotation else { return }
        
        switch type {
        case let .image(image):
            backgroundColor = .clear
            let count = annotation.annotations.count
            let scale = self.scale(for: count)
            
            countLabel.text = "\(count)"
            countLabel.textColor = pinTextColor
            
            self.image = image?.imageResize(sizeChange: CGSize(width: 50*scale, height: 50*scale))
            
        case let .color(color, radius):
            let count = annotation.annotations.count
            backgroundColor    = color
            var diameter = radius * 2
            switch count {
            case _ where count < 8: diameter *= 0.6
            case _ where count < 16: diameter *= 0.8
            default: break
            }
            frame = CGRect(origin: frame.origin, size: CGSize(width: diameter, height: diameter))
            countLabel.text = "\(count)"
        }
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        countLabel.text = nil
        image = nil
    }
    
    
    func updateFor(annotation: ClusterAnnotation, with type: ClusterAnnotationStyle){
        self.annotation = annotation
        configure(with: type)
    }
    
    
    private func scale(for annotationsCount: Int) -> CGFloat {
        switch annotationsCount {
        case _ where annotationsCount < 25: return  0.75
        case _ where annotationsCount < 50:  return 1
        case _ where annotationsCount < 100: return 1.25
        case _ where annotationsCount >= 100: return 1.5
            
        default: return 1
        }
    }
    
    
    
}

Cluster goes crazy

I try latest release 2.0.1 and the cluster goes crazy. When I zoom on the map, it does not stop changing its position.

Help, please.

When single annotation tapped, how do I get the index of the backing data

When single annotation tapped, how do I get the index of the backing array of data

New Issue Checklist

Issue Description

I have an array of objects, e.g. Shop object, and for each I have created an Annotation object and added them to the ClusterManager
When user taps on a single annotationView and mapView: didSelect: is called, how do I get the correct index of the Shop array? This should be the Shop at the coordinates that the user has tapped on

Environment

  • iOS Version: Any
  • Device(s): Any

BorderedClusterAnnotationView borderColor conflicts error

Hello,
Thanks for this useful library. I get an error implement it in my project just like your example. Example project is working properly but When I add library to my project and use it I got this error.

Getter for 'borderColor' with Objective-C selector 'borderColor' conflicts with getter for 'borderColor' from superclass 'UIView' with the same Objective-C selector

in here:

class BorderedClusterAnnotationView: ClusterAnnotationView {
    let borderColor: UIColor   //I got error on this line

What is the problem? I can't solve it.

Thanks

Unable to access ClusterAnnotationView type:ClusterAnnotationType property

Hello,

commit "710c605aa47f92da52f7f7f5618650ca2a28e64a" changed protection attribute of type property.

OLD
public var type: ClusterAnnotationType? {
         didSet {
             configure()
         }
     }
NEW
let type: ClusterAnnotationType

What was the reasoning behind this move? Could you please change it back to public?

Thanks a lot.

Cluster disappears when max zoomed out

Hello,

I've faced an issue where I have a map with about 25 pins. Half of them is located in Asia and the other half in US. When I zoom out the map and look at the Asian cluster everything works great. But when I swipe the map to the US cluster, it simply does not display at the same zoom level. When zoom it in a little bit, then it appears. US cluster is a little bit smaller e.g. Asian 14 pins vs 11 pins in US.

As I understand that behaviour has something with spatial division of pins in clusters. The Asian cluster is a 'parent' one for US, that is why they both cannot be displayed at the same zoom level.

The question is: How to display the US cluster when map is zoomed out at max distance?

Disable clustering from certain zoom level

Hello again!

It would be great to be able to disable clustering from a certain zoom level. For instance it can be a property or a method e.g. :

clusterManager.zoomLevel = 10

Where:

  • clusterManager is an instance of Clusterclass
  • zoomLevel is a property that takes a numeric parameter

The inspiration was taken from Google Maps API.

Thank you for doing an amazing job!

Several exceptions

I run the app, load the annotations in manager and the apps wrong.

This is a capture of this, with the line when is the error.

captura de pantalla 2017-10-20 a las 13 27 27

Pins are clustered into two cluster, that are overlapping each other

Hey!

Sometimes pins are clustered into more than one cluster, and these clusters are overlapping each other. That is really confusing, sometimes two overlapping cluster pins are exactly on the same place on the map, which looks like a mistake in calculation.

Is that normal, expected behaviour of the framework?

screenshot at oct 07 16-56-42

Select annoation view after adding annoation to cluster manager

I try to select an annotation right after adding it to the cluster manager. To make it appear on the map I have to call clusterManager.reload. However this method is async. In my case I try to select the annoation in code right after calling reload. However the annoation is not available yet.

        clusterManager.add(annotation)
        clusterManager.reload(mapView, visibleMapRect: mapView.visibleMapRect)
        mapView.selectAnnotation(annotation, animated: true) // can not find annotation

Is there any way I can know when reload has finished? Can you add a callback?

Non-clustered annotations get removed when reloading cluster manager

I'm working with a map which has both: annotations managed by a ClusterManager and other annotations which are not clustered at all.

I've found that when one of those non-clustered annotations is visible and I call clusterManager.reload(_:, visibleMapRect:) the non-clustered annotation gets removed.

Checking source code of release 1.0.7 I've found this:

https://github.com/efremidze/Cluster/blob/1.0.7/Sources/Cluster.swift#L168

let before = NSMutableSet(array: mapView.annotations)
before.remove(mapView.userLocation)

This takes all annotations in the map (regardless they are managed by this cluster or not) and removes them according to what the manager decides to show or not.

Changing this by:

let annotationsInCluster = NSSet(array: self.annotations)
let before = NSSet(
    array: mapView.annotations.filter {
        $0 is ClusterAnnotation || annotationsInCluster.contains($0)
    }
)

Seems to solve my problem.

I would normally open a pull request but I don't have any test for this use case and I'm not quite sure why that line was like it was before so until I have some feedback I won't open a pull request.

PS: Nice library!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.