choefele / cchmapclustercontroller Goto Github PK
View Code? Open in Web Editor NEWHigh-performance map clustering with MapKit for iOS and OS X. Integrate with 4 lines of code.
License: MIT License
High-performance map clustering with MapKit for iOS and OS X. Integrate with 4 lines of code.
License: MIT License
Hey,
I've been looking into my crash reports and i found one that happened multiple of times. it's inside CCHMapClusterControllerUtils.m so i am not really able to debug the issue
it's line 53 CCHMapClusterControllerFindVisibleAnnotation
the error is
Fatal Exception: NSInvalidArgumentException
-[__NSCFString containsObject:]: unrecognized selector sent to instance 0x1d31d780
line 53 is
if ([visibleAnnotation.annotations containsObject:annotation]) {
so i don't understand how visibleAnnotation.annotations was pointing to a NSString.
could you help me in any way?
If the map is zoomed all the way in, some annotations are still clustered and are thereby inaccessible.
Clusters should only be shown if it is still possible to zoom in.
If I am doing another method on the map that requires touches and want it so the user isn't able to click on the cluster annotation or any annotation for that matter and it do anything, just to have them disabled, is there a way to do this?
Thank You
Blake Mitchell
i changed my map view constraints to now fill the view to the top because i occasionally hide the navigation bar. when i present a modal view and then close it
i get an error
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid map length'
on line CCHMapClusterController/CCHMapClusterControllerUtils.m:90
Probably I've missed something here. Question is; should mapView:annotationView:calloutAccessoryControlTapped: supposed to work or should I use something else?
Not called in either the example project or in my own project.
Hi, first thank you for this project I am really forward to digup more about it. But There is something I can't figure out. I am trying to display one single annotation in France with both JSON and CSV. Can you explain me why it does not work even if I am changing the region, the annotation is always display lat: 0 long: 0, like it does not have any coordinate.
Just getting start with CCHMapClusterController
. I would like to use it for bikemap.
I need to display how many annotations are in a grid cell like you can see here…
http://www.bikemap.net/en/?tab=top#/z6/50.00773,-4.57032/terrain
It looks as though this is not possible at the moment.
Could you point me in the right direction?
It appears that I need to refactor the CCHMapClusterAnnotation
part in updateAnnotationsWithCompletionHandler:
so I can override it in a subclass which sums up the visible annotation's sub-annotations.
It would be nicer if I could somehow work this out from within the mapClusterController:titleForMapClusterAnnotation:
delegate method.
For instance:
If I add 100 annotations in NJ. then I move to PA and add new pins (and still leaving the pins in nj). Then scroll back to the pins in jersey they seem to "double", meaning there are two annotations in the place of one for whatever reason in the same geo (note I do not fetch for more pins).
All the callbacks from CCHMapClusterController should be delegated to the main queue as it's most likely to always be used in conjunction with other main thread work and a MKMapView.
Right now many of the completionHandlers are always or sometimes set directly to an NSOperation's completionBlock property. When doing so the thread in which the callback is made is undefined as it will be whatever thread the NSOperation runs on.
Hi
Type casting not working in my code.
clusterAnnotationView is already MkpinAnnotation object. So give "[MKPinAnnotationView setCount:]: unrecognized selector sent to instance" error.
What should I do??
Thanks
i use
[_mapView removeAnnotations:_mapView.annotations];
to remove the annotations from the map but they come back. is this because i haven't removed them from my CCHMapClusterController?
if so how can i remove them?
Tapping "Reset" button results in another bunch of annotations added to the map without clearing the old ones.
Steps to reproduce:
Expected results:
Map is reloaded with the same number of annotations
Actual results:
The same number of annotations is added to the map, doubling the numbers displayed for the first time, 3x, 4x and so on, every time you tap "Reset".
I have a map view using your great CCHMapClusterController. Clusters with more than one annotations are shown with a circle and a number, clusters with a single annotation are shown with a standard pin. Tapping on clusters with more than one annotation will zoom in to show the cluster, tapping on the other annotations will bring up a callout with some detail information.
My problem is now related to the callout of the single location annotation. When I tap on the annotation and the annotation callout can be shown without changing the region of the map view (which is done by the map view implicitly) everything is fine. But when I tap on it when it is next to the bounds of the map view and the map view has to change its region to show the callout without cutting it, the callout disappears right after it appeared. In the debug log this message appears:
Expected window when dismissing popover view in order to set rasterization scale. Using mainScreen scale instead. popoverView = <_UIPopoverView: 0xa989550; frame = (-73 -57; 146 57); layer = <CALayer: 0x26ec06e0>>
The other problem is that when I opened a callout of an annotation and then drag the map view and change its region (no zooming) then the callout is closed.
I have no idea why this happens and I couldn't find anything on the web. I guess that this is related to the cluster controller (the code I use is commented), because when I add the annotations without clustering everything works fine.
A screenshot and the code can be found below. Maybe somebody has an idea and could help me out.
#import "StationsMapViewController.h"
#import <CCHMapClusterController.h>
#import <CCHMapClusterAnnotation.h>
#import "Station.h"
#import <MKMapViewUtil.h>
#import "StationAnnotation.h"
#import "StationsContainerViewController.h"
@interface StationsMapViewController () <MKMapViewDelegate>
@property (strong, nonatomic) NSArray *stations;
@property (strong, nonatomic) CLLocation *currentLocation;
@property (strong, nonatomic) CCHMapClusterController *mapClusterController;
@end
@implementation StationsMapViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.mapClusterController = [[CCHMapClusterController alloc] initWithMapView:self.mapView];
self.mapClusterController.cellSize = 100;
self.mapClusterController.reuseExistingClusterAnnotations = NO;
}
- (void)showStations:(NSArray *)stations withCurrentLocation:(CLLocation *)currentLocation {
self.stations = stations;
self.currentLocation = currentLocation;
[self.mapClusterController removeAnnotations:self.mapClusterController.annotations.allObjects withCompletionHandler:nil];
// [self.mapView removeAnnotations:self.mapView.annotations];
if (self.stations.count > 0) {
NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:stations.count];
for (Station *station in stations) {
StationAnnotation *annotation = [[StationAnnotation alloc] init];
annotation.station = station;
[annotations addObject:annotation];
}
[self.mapClusterController addAnnotations:annotations withCompletionHandler:NULL];
// [self.mapView addAnnotations:annotations];
MKCoordinateRegion region = [MKMapViewUtil regionThatFitsAnnotations:annotations];
region = [self.mapView regionThatFits:region];
[self.mapView setRegion:region animated:YES];
}
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if ([annotation isKindOfClass:CCHMapClusterAnnotation.class]) {
// if ([annotation isKindOfClass:StationAnnotation.class]) {
CCHMapClusterAnnotation *clusterAnnotation = (CCHMapClusterAnnotation *)annotation;
if (clusterAnnotation.isCluster) {
static NSString *clusterAnnotationViewIdentifier = @"ClusterAnnotationView";
static NSInteger labelTag = 10000;
MKAnnotationView *annotationView = (MKAnnotationView *) [mapView dequeueReusableAnnotationViewWithIdentifier:clusterAnnotationViewIdentifier];
if (annotationView == nil) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
label.tag = labelTag;
label.textAlignment = NSTextAlignmentCenter;
label.font = [UIFont boldSystemFontOfSize:16];
label.backgroundColor = [UIColor clearColor];
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:clusterAnnotationViewIdentifier];
annotationView.canShowCallout = NO;
[annotationView addSubview:label];
annotationView.centerOffset = CGPointMake(-20, -20);
annotationView.image = [UIImage imageNamed:@"clusterAnnotationImage"];
}
else {
annotationView.annotation = annotation;
}
((UILabel *) [annotationView viewWithTag:labelTag]).text = [NSString stringWithFormat:@"%lu", (unsigned long)clusterAnnotation.annotations.count];
return annotationView;
}
else {
static NSString *stationAnnotationViewIdentifier = @"StationAnnotationView";
MKPinAnnotationView *annotationView = (MKPinAnnotationView *) [mapView dequeueReusableAnnotationViewWithIdentifier:stationAnnotationViewIdentifier];
if (annotationView == nil) {
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:stationAnnotationViewIdentifier];
annotationView.pinColor = MKPinAnnotationColorRed;
annotationView.animatesDrop = NO;
annotationView.canShowCallout = YES;
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
else {
annotationView.annotation = annotation;
}
StationAnnotation *stationAnnotation = [[clusterAnnotation.annotations objectEnumerator] nextObject];
clusterAnnotation.title = stationAnnotation.title;
clusterAnnotation.subtitle = stationAnnotation.subtitle;
return annotationView;
}
}
return nil;
}
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
CCHMapClusterAnnotation *clusterAnnotation = (CCHMapClusterAnnotation *)view.annotation;
if (clusterAnnotation.isOneLocation) {
StationAnnotation *stationAnnotation = [[clusterAnnotation.annotations objectEnumerator] nextObject];
[self.stationsContainerViewController showStationDetailWithStation:stationAnnotation.station];
}
}
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
if ([view.annotation isKindOfClass:CCHMapClusterAnnotation.class]) {
CCHMapClusterAnnotation *clusterAnnotation = (CCHMapClusterAnnotation *) view.annotation;
if (clusterAnnotation.isCluster) {
CCHMapClusterAnnotation *clusterAnnotation = (CCHMapClusterAnnotation *) view.annotation;
MKMapRect mapRect = [clusterAnnotation mapRect];
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(90, 25, 80, 25);
[mapView setVisibleMapRect:mapRect edgePadding:edgeInsets animated:YES];
}
}
}
@end
Hello
This is the case when the data is already loaded completely and the user quickly switches to map portions of coordinates. For example, the countries
How to reproduce:
1 "CCHMapClusterController Example iOS" - DataReader.m
method "dispatchAnnotations" - comment out "[NSThread sleepForTimeInterval: DELAY_BETWEEN_BATCHES]"
2 frequently and quickly press the "reset"
CCHMapClusterController Example iOS[4322:60b] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[__NSPlaceholderSet initWithObjects:count:]: attempt to insert nil object from objects[9972]'
sometimes falls out of memory
With version 1.6.4 we have started to see this crash in our logs. Fortunately it happens very rarely. We haven't been able to reproduce it.
-[CCHMapViewDelegateProxy mapView:didUpdateUserLocation:]: unrecognized selector sent to instance 0x17baa680
0 CoreFoundation 0x2c529e3f + 126
1 libobjc.A.dylib 0x39c16c8b objc_exception_throw + 38
2 CoreFoundation 0x2c52f189 + 0
3 CoreFoundation 0x2c52d0a7 + 714
4 CoreFoundation 0x2c45f208 _CF_forwarding_prep_0 + 24
5 MapKit 0x2dc4eef3 redacted + 2130
6 CoreFoundation 0x2c4657f9 + 204
7 MapKit 0x2dc14fed redacted + 76
8 MapKit 0x2dc14c79 redacted + 1248
9 MapKit 0x2dc145bd redacted + 608
10 MapKit 0x2dc1434f redacted + 62
11 MapKit 0x2dc140f7 redacted + 302
12 CoreLocation 0x2cbe7e65 CLClientGetCapabilities + 21688
13 CoreLocation 0x2cbe3d0b CLClientGetCapabilities + 4958
14 CoreLocation 0x2cbddaf7 CLClientInvalidate + 966
15 CoreFoundation 0x2c4f05b5 + 12
16 CoreFoundation 0x2c4ef879 + 216
17 CoreFoundation 0x2c4ee3b3 + 1714
18 CoreFoundation 0x2c43c621 CFRunLoopRunSpecific + 476
19 CoreFoundation 0x2c43c433 CFRunLoopRunInMode + 106
20 GraphicsServices 0x337eb0a9 GSEventRunModal + 136
21 UIKit 0x2fa27359 UIApplicationMain + 1440
22 Rabble 0x00092169 0x8a000 + 33129
23 libdyld.dylib 0x3a196aaf + 2
Hello.
First of all, thanks for nice framework, working for me like a charm.
I have a question. I am moving a camera manually (some animation), and when I moving it, framework trying to make clusterisation every frame (I think it;s because of setCamera calls). Is there any way to manually stop and then resume clusterisation?
It will be nice if I'll can stop clusterisation, make animation, and the restart it.
Thanks for your time, Karen.
I am having kind of the same issue as in #23 .
If an annotation isOneLocation
I'd like to show an image instead of the default MKAnnotationView
pin. I do that by simple setting an image on the annotationView.
But... if the location is not one location, I want to show a complete custom AnnotationView. So I subclassed MKAnnotationView
and overwrote the drawRect method... (It's just a circle with a number in it ;-) )
How can I now tell CCHMapClusterController
that he needs to go and get a new view for that annotation if the annotations changes it's isOneLocation
property?
I already found mapClusterController:willReuseMapClusterAnnotation:
but I can't change the view class that's being used for the annotation, right?
Hi Claus,
I've noticed the clusters to rearrange when I programmtically change the region of the map. When the user is moving the map by hand, everything is fine and the clusters stay the same. But whenever I use setCenterCoordinate:animated:
or setRegion:animated:
, the annotation clusters rearrange themselves. I've noticed that the CCHMapClusterController
stores the MKCoordinateSpan
right before the region is changed, and compares it to the new span afterwards.
In the documentation of setCenterCoordinate:animated:
Apple promises:
Changing the center coordinate centers the map on the
new coordinate without changing the current zoom level.
This is true from a human point of view. At least I cannot see the map zoom in. But the span value changes slightly. I've created a branch here to illustrate the issue: https://github.com/plu/CCHMapClusterController/compare/regionSpanBeforeChange
There are three commits. If you reset to the first one (5451b4f) and tap on any of the annotations, you'll see that while it's being centered, the clusters are rearranged. The other two commits are not meant to be a proper fix. Just to illustrate the difference.
Do you have any idea how to properly fix this?
Best regards,
plu
Because I add annotations when the region changes I end up having updateAnnotationsWithCompletionHandler
called twice.
Perhaps we could fix this by providing a setting like CCHMapClusterController.shouldUpdateOnRegionDidChange
with a default value of YES.
I could then skip updateAnnotationsWithCompletionHandler
in CCHMapClusterController's mapView:regionDidChangeAnimated:
Hi,
I am facing 2 issues which are mentioned below:
Map view zooms out [earlier zoom level is around 17 and after running above lines of code zoom level is 16], due to which the annotations are clustered again and "didDeselectAnnotationView" is called. Due to this, user is never able to view these annotations, every-time the view is zoomed out. If user try to manually zoom in again, then again the view is zoomed out.
It works perfectly till zoom level reached around 17, but after that i again want to zoom further, need zoom level of 18.5 approx., but it does not allow to zoom. Rather, it keeps on showing the same location again and again.
My need is that, if the annotation is clustered, then zoom further till one unique location is found. And once that unique location is found, show the annotation view.
Sample code is here (includes the code for both the above mentioned issues):
(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(ClusterAnnotationView *)view {
if ([view.annotation isKindOfClass:[MKUserLocation class]]) {
}
else{
CCHMapClusterAnnotation *clusterAnnotation = view.annotation;
if (clusterAnnotation.isUniqueLocation == FALSE){
CCHMapClusterAnnotation *clusterAnnotation = (CCHMapClusterAnnotation *)view.annotation;
MKMapRect mapRect = [clusterAnnotation mapRect];
CGFloat val = 10;
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(val, val, val, val);
[mapView setVisibleMapRect:mapRect edgePadding:edgeInsets animated:YES];
}
else{
// mapView.centerCoordinate = view.annotation.coordinate;
MKMapPoint point = MKMapPointForCoordinate(view.annotation.coordinate);
MKMapRect rect = [mapView visibleMapRect];
rect.origin.x = point.x - rect.size.width * 0.5;
rect.origin.y = point.y - rect.size.height * 0.5;
[mapView setVisibleMapRect:rect animated:YES];
[customCalloutView setTag:kTag_CustomCalloutView];
[view addSubview:customCalloutView];
[customCalloutView setCenter:CGPointMake(view.bounds.size.width * 0.5f, -customCalloutView.frame.size.height * 0.5f)];
NSArray *myArray = [clusterAnnotation.annotations allObjects];
NSMutableArray *tempArr = [[NSMutableArray alloc] init];
for (int _i = 0; _i < [myArray count]; _i++){
CustomPointAnnotation *pointAnnotation = [myArray objectAtIndex:_i];
[tempArr addObject:searchResultsArray[pointAnnotation.tag]];
}
annotationzArray = nil;
annotationzArray = tempArr;
[annotationzTV reloadData];
}
}
}
Please respond asap.
Thanks in advance!
Hey,
i have a small issue that i would love if you can help me with.
when i press a cluster i push another view controller but when i get back to the map i am unable to select that cluster again unless i pinch the map a little to zoom in or out.
it just ignores my touches till i do that.
do you have any idea what might be causing that?
How hard would it be to port this implementation into GMSMapView?
Hello guys,
I'm having some trouble using removeAnnotations:withCompletionHandler
when updating my annotations after adding them to the map.
The problem is that after updating the coordinate of my annotations, some of them are never removed from the map. From what I found ( guess ), CCHMapTreeNodeRemoveData(_root, data))
is in charge of removing the node from the map, internally it calls CCHMapTreeBoundingBoxContainsData
to see if the data is inside the bounding box of the node. I thinks the problem here is that the bounding box of the tree is outdated due to the update in the coordinate.
I'm using Core Data with my application for backing up my annotations. Map Kit uses KVO to observe the coordinate property of the annotation and update the position if it changes, I think CCHMapClusterController doesn't have the same behaviour.
Let me know if anything of what I said makes sense.
Thanks and keep up the good work.
When I try to addAnnotations:withCompletionHandler:
the completion block is being called before all annotations are added in the map.
I have a method when I try to recenter the map region based on its annotations and this method must be called after all annotations get pinned.
I believe that this is a bug. If not, the parameter name should be changed because completionHandler
gives the understanding that this is called after everything is done.
To reproduce:
Result: debug grid doesn't get displayed correctly.
This used to work by subtracting MKMapSizeWorld.width
in updateDebugPolygonsInGridMapRect:withCellMapSize:
, but broke in iOS 7.1.
Ive implemented the MkMapViewDelegate to get called when an annotation is selected; on my code I want to zoom in the annotation, I call this code
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
[_mapClusterController selectAnnotation:view.annotation
andZoomToRegionWithLatitudinalMeters:1000.0f
longitudinalMeters:1000.0f];
}
then there is a bug, the map si zoomed correctly but then it moves to a region with center 0,0
I found the problem to be in here
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
...
if (self.annotationToSelect) {
// Map has zoomed to selected annotation; search for cluster annotation that contains this annotation
CCHMapClusterAnnotation *mapClusterAnnotation = CCHMapClusterControllerClusterAnnotationForAnnotation(self.mapView, self.annotationToSelect, mapView.visibleMapRect);
...
at this point for some reason the mapClusterAnnotation is nil , the test that check if the map view centerCoordinate is the same will fail and then map is moved on the west coast of Africa :-)
[self.mapView setCenterCoordinate:mapClusterAnnotation.coordinate animated:NO];
At the moment my work around is to setVisibleMapRect by hand but this means that when the map is zoomed in the cluster annotation for that region may not be there anymore (which may also explain the reason I've the bug, maybe i zoom too much close)
Hey mate,
it is possible to add 2 or more different trees?
The thing is, I have got more then 1 kind of Annotations with different pin color and would like to separate this pins onto the map.
Cheers,
Eike
Here is the backtrace on Crashlytics. http://crashes.to/s/0fd17684020
I was not able to reproduce the crash myself though...
Both addAnnotations:withCompletionHandler: and removeAnnotations:withCompletionHandler: take a completion handler, but that's going to be called before the cluster actually finished updating its annotation view. That's because mapClusterController:willReuseMapClusterAnnotation:, which presumably updates the annotation view, gets dispatch_async(ed) on the main queue, and might be executed after the completionBlock of CCHMapClusterOperation.
This means that the clustering may still be changing, at least visually, after the completion handlers get called.
this is an example of how i setup my annotationView in viewForAnnotation
my problem is that i have to exactly touch the centre of the annotationView to actually select it.
i tried setting all the subviews UserInteractionEnabled property to YES but it didn't make any difference.
MKAnnotationView *annotationView = (MKAnnotationView *) [mapView dequeueReusableAnnotationViewWithIdentifier:kPictureAnnotationIdentifier];
if (annotationView == nil) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:kPictureAnnotationIdentifier];
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"whiteBackground"]];
backgroundImageView.center = annotationView.center;
[annotationView addSubview:backgroundImageView];
UIImageView *imageView = [[UIImageView alloc] initWithImage:pictureAnnotation.thumbnailImage];
imageView.center = annotationView.center;
[imageView setTag:2];
[annotationView addSubview:imageView];
annotationView.enabled = YES;
[annotationView sendSubviewToBack:backgroundImageView];
}else {
annotationView.annotation = annotation;
UIImageView *imageView = (UIImageView *)[annotationView viewWithTag:2];
[imageView setImage:pictureAnnotation.thumbnailImage];
[annotationView bringSubviewToFront:imageView];
}
as always i truly appreciate your support
Xcode 6 seems to have an issue automatically synthesizing properties that have a custom setter method. Therefore, CCHMapClusterOperation
will not compile. This easily fixed by adding
@synthesize executing = _executing;
@synthesize finished = _finished;
to the @implimentation
block. Just wanted to make you aware.
I might be overlooking where to do this but I am querying for objects from Parse and populating the map. I have pretty much everything integrated but I want a user to be able to click on a cluster icon and then it displays the objects that are in that cluster. what is the best way I should approach this? I read that it might not be possible to display individual annotations? I tried changing the maxZoomLevel but didn't have any luck. Would there be a a way I could get the objects of that cluster to display them elsewhere? Any help would be appreciated.
Thank you
-Blake
i hope i can explain this simply. here goes.
i have a cluster with 9 annotations. ( i show the number 9 on the cluster annotation)
when i zoom in a little another annotation pops up on its own and the cluster ( but now the cluster count should be 8 but it stays at 9) when i zoom in again the count is updated to 8.
if i zoom in a lot till i can see all the separate annotations and then zoom out again fully i don't see the count 9 straight away. however i see a random annotation ( either a number of one of the smaller clusters or on of the annotations on their own) if i move away from this location on the map and then come back i see the number 9 again as viewForAnnotation gets called normally.
do you get what i mean?
is it something on my side of the code?
thank you very much for your support
Hello.
Can you help me please, I'm using this code for creating the clusterer.
if (self.mapClusterController == nil)
{
self.mapClusterController = [[CCHMapClusterController alloc] initWithMapView: map];
self.mapClusterController.delegate = self;
self.mapClusterController.maxZoomLevelForClustering = 15;
self.mapClusterController.cellSize = 10;
self.mapClusterController.marginFactor = 0.3;
self.mapClusterer = [[CCHNearCenterMapClusterer alloc] init];
self.mapClusterController.clusterer = self.mapClusterer;
self.mapAnimator = [[CCHFadeInOutMapAnimator alloc] init];
self.mapClusterController.animator = self.mapAnimator;
}
[self.mapClusterController removeAnnotations: annotations withCompletionHandler: NULL];
[self.mapClusterController addAnnotations: annotations withCompletionHandler: NULL];
However clustering is not disabling even when zoomlevel is 17 or 18.
I getting zoom level from clusterer (for debug) so it knows about current zoom level.
What I'n doing wrong?
Hello!
I don't know why, but running the Example Project, the delegate method "didSelectAnnotationView" is called, but in my project not.
I've implemented using example project as guide.
Property "canShowCallout" is "YES".
Is there something that I need to check?
Hello, is there any simple way to change the annotation color on the map? Would I just have to extract the images and change them manually? Would you by chance have the sizes of them? Any help would be appreciated. Thank You
Hi @choefele,
We have encountered a crash that happens for more than a few users of our app.
Crash occurs in CCHMapClusterController updateAnnotationsWithCompletionHandler
, at line 219.
See the crashlog from Crashlytics.
We are using the version 1.5.0 via CocoaPods.
The exception:
Fatal Exception: NSInvalidArgumentException
*** -[__NSSetM addObject:]: object cannot be nil
The line that causes the crash
NSSet *annotationsBeforeAsSet = CCHMapClusterControllerClusterAnnotationsForAnnotations(self.mapView.annotations, self);
My opinion is that the crash is caused by self.mapView.annotations
being executed on the background queue.
I noticed that in the most recent master branch, this code has been refactored so that the call to [MKMapView annotations]
is made in [CCHMapClusterOperation initWith...]
. The init method are always called on main thread, as the updateAnnotationsWithCompletionHandler
method is called on the main thread and this is the only place where CCHMapClusterOperation
objects are created.
Q1: Does this crash/crashlog tell you anything? Do you have more insight into this?
Q2: If we can assume that was the cause of the crash and it's fixed on the master branch, could you release a new version for CocoaPods?
Thanks,
Bogdan
I do not add all of the annotations at once. I add new annotations every time I go to a new region. For this reason I add the same annotation (different objects) multiple times.
In order for the following code in CCHMapClusterController
to work…
// Figure out difference between new and old clusters
NSMutableSet *annotationsBefore = [NSMutableSet setWithArray:_mapView.annotations];
[annotationsBefore removeObject:[_mapView userLocation]];
NSMutableSet *annotationsToKeep = [NSMutableSet setWithSet:annotationsBefore];
[annotationsToKeep intersectSet:clusters];
NSMutableSet *annotationsToAddAsSet = [NSMutableSet setWithSet:clusters];
[annotationsToAddAsSet minusSet:annotationsToKeep];
NSArray *annotationsToAdd = [annotationsToAddAsSet allObjects];
NSMutableSet *annotationsToRemoveAsSet = [NSMutableSet setWithSet:annotationsBefore];
[annotationsToRemoveAsSet minusSet:clusters];
NSArray *annotationsToRemove = [annotationsToRemoveAsSet allObjects];
…CCHMapClusterAnnotation
needs custom isEqual:
and hash
methods. I added the following less than perfect implementations.
- (BOOL)isEqual:(id)object
{
if ([[object class] isSubclassOfClass:[CCHMapClusterAnnotation class]])
{
CCHMapClusterAnnotation *other = (CCHMapClusterAnnotation *)object;
if (other.coordinate.latitude == self.coordinate.latitude
&& other.coordinate.longitude == self.coordinate.longitude) return YES;
}
return NO;
}
- (NSUInteger)hash
{
return @(self.coordinate.longitude).hash ^ @(self.coordinate.latitude).hash;
}
Without these, each annotation is removed and added again when I pan the map.
Hi,
I have some trouble to detect click on my AnnotationView.
I write the following didSelectAnnotationView :
Unfortunately, this method is never called when I click on my AnnotationVIew, except if the following method returns a NSString :
So, this will displayed a bubble with a space but I don't need any bubble and title.
Do you have any idea what is the issue please ?
Hi,
I'd like to have my pins moving from cluster position to their own position and viceversa (like the photo application). Unfortunately I don't see how I could implement this with the map animator protocol, for instance how can I get the "parent" location when an annotation is moved in/out from a cluster ?
thanks
Hey!
I wonder if it is possible to get a list of all root annotations currently displayed on the map.
By root annotations i mean all my custom annotations either withheld within a cluster or solo standing on the map.
/ Simon
Would it be possible to only add rightCalloutAccessoryView & leftCalloutAccessoryView to the annotations which is not clustered? Right now, if I add the AccessoryViews it is also displayed on the clustered annotations.
How would I manage to only show the accessoryViews on the nonclustered annotations / one location annotations?
I have tried the following in the mapView:(MKMapView*)mapView viewForAnnotation:(id)annotation method, but it does not work:
if ([annotation isKindOfClass:[MOMapClusterAnnotation class]]) {
annotationView.canShowCallout = YES;
annotationView.count = [(MOMapClusterAnnotation*)annotation annotations].count;
annotationView.innerCircleFillColor = [UIColor myOrderThemeColor];
if ([(CCHMapClusterAnnotation*)annotation annotations].count == 1 && [(MOMapClusterAnnotation*)annotation isOneLocation] ) {
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.leftCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
}
Thank you for the help in advance :-)
Thanks for this great library.
i just wanted to ask something. In my scenario i show pins on a map which represent images. However someone might have shot several images on a single location. This means that even on the max zoom i would show a cluster instead of individual pins.
Is there a way to know the pins inside each cluster?
For example long tap the cluster to NSLog which objects are inside.
I can then use this info and show a pop up with the thumbs of the images inside that cluster.
Any help would be great
Thanks again
Konstantinos
It would be really nice if there was a way to solve this in this otherwise fantastic project. The problem is that when user selects an annotation I need it to change its icon and also the previous selected annotation needs to change its icon back to what it was before.
The best thing would be if it didn't become clustered so it always is visible.
In mapView:viewForAnnotation I've setup a check to see if annotation is selected and give it the right icon. It works for the newly selected annotation, but the previous selected doesn't update.
I tried removing it and adding it in didSelectAnnotationView but it doesn't work all the time:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
CCHMapClusterAnnotation *anno = view.annotation;
[self.mapClusterController removeAnnotations:@[self.previousSelectedAnnotation] withCompletionHandler:^{
[self.mapClusterController addAnnotations:@[self.previousSelectedAnnotation] withCompletionHandler:nil];
}];
}
I'm thinking maybe there could be two clusterControllers where only the selected annotation could be in one of them. Removing annotations from one and add it to another.. is that a good idea?
I'm working on a map and use multiple cluster controllers because I have different types of annotations. I'm using the mapClusterController:titleForMapClusterAnnotation:
delegate method to provide a title to my cluster annotations.
Problem is the mapClusterController
variable is always nil : https://github.com/choefele/CCHMapClusterController/blob/master/CCHMapClusterController/CCHMapClusterAnnotation.m#L33-L49
I'd be more than happy to provide a quick fix for this, but maybe there is a reason why it was made this way?
Thanks for a great lib.
I am having trouble with the annotation callouts and I do not know what I am doing wrong.
When I enter my view controllers view, the annotations are clustered perfectly.
When i then zoom in on my annotations they are declustered as expected.
However, when i zoom back out and the annotations starts to group up to clusters again, some clusters have the image of a single annotation. The same thing goes for their callouts. They have the titles and subtitles of my clustered annotations, but they have the disclosure button which i have only assigned to single, unclustered annotations.
This is how i initialize my cluster controller:
...
// viewDidLoad
self.mapClusterController = nil;
self.mapClusterController = [[CCHMapClusterController alloc] initWithMapView:self.mapView];
self.mapClusterController.delegate = self;
self.mapClusterController.cellSize = 25;
self.mapClusterController.marginFactor = 0.4;
//
...
This is how i populate the controller with my own custom annotations:
//...
[self.mapClusterController removeAnnotations:[self.mapClusterController.annotations allObjects] withCompletionHandler:nil];
NSMutableArray *arrayWithAnnotations = [[NSMutableArray alloc] init];
FFMapAnnotation *annotation;
for(FFPlace *place in self.searchResult){
annotation = [[FFMapAnnotation alloc] init];
annotation.title = place.placeName;
annotation.coordinate = CLLocationCoordinate2DMake(place.placeLatitude, place.placeLongitude);
annotation.place = place;
annotation.subtitle = place.placeAddress;
[arrayWithAnnotations addObject:annotation];
}
[self.mapClusterController addAnnotations:arrayWithAnnotations withCompletionHandler:nil];
//...
Now, I do believe that these two methods work correctly.
I imagine that the problem lies within the viewForAnnotation method.
This is currently how it looks:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView *annotationView;
if ([annotation isKindOfClass:CCHMapClusterAnnotation.class]) {
static NSString *identifier = @"clusterAnnotation";
annotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView) {
annotationView.annotation = annotation;
} else {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
annotationView.canShowCallout = YES;
}
CCHMapClusterAnnotation *clusterAnnotation = (CCHMapClusterAnnotation *)annotation;
clusterAnnotation.delegate = self;
if(!clusterAnnotation.isCluster){
FFMapAnnotation *annot2 = (FFMapAnnotation*)clusterAnnotation.annotations.allObjects[0];
annotationView.image = [UIImage imageNamed:[DefinedCategories getCategoryInformationForID:annot2.place.placeMainCategoryID].mapImage];
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeContactAdd];
annotationView.rightCalloutAccessoryView.tag = -1;
}else {
return nil;
}
}
return annotationView;
}
// the return nil at the end makes the annotation view a regular MKPinAnnotationView, which i use to visually represent my clusters.
Finally, the two delegate methods for cluster title and subtitle looks like this:
- (NSString *)mapClusterController:(CCHMapClusterController *)mapClusterController titleForMapClusterAnnotation:(CCHMapClusterAnnotation *)mapClusterAnnotation
{
NSString *title;
if(mapClusterAnnotation.annotations.count > 1){
title = [NSString stringWithFormat:@"%ld %@", (unsigned long) mapClusterAnnotation.annotations.count, [AppDelegate get:@"PLACES"alter:nil]];
} else{
FFMapAnnotation *annotation = mapClusterAnnotation.annotations.allObjects[0];
return annotation.title;
}
return title;
}
- (NSString *)mapClusterController:(CCHMapClusterController *)mapClusterController subtitleForMapClusterAnnotation:(CCHMapClusterAnnotation *)mapClusterAnnotation
{
NSString *title;
if(mapClusterAnnotation.annotations.count > 1){
title = [AppDelegate get:@"ZOOM_FURTHER"alter:nil];
} else{
FFMapAnnotation *annotation = mapClusterAnnotation.annotations.allObjects[0];
return annotation.subtitle;
}
return title;
}
What i want :
Am I missing something vital in the delegate methods? I spent all day debugging this.
Thank you very much for this awesome lib.
If there are multiple annotations with the same coordinate, setting maxZoomLevelForClustering doesn't disable clustering ,if we zoomed in further.
I also tried with minUniqueLocationsForClustering = 2. That also not working in this scenario. Please advise.
Calling this from didSelectAnnotationView leaves the map animating back and forth. I'll see if I can write a spec, and/or replacement.
I also assumed it could be passed a CCHMapClusterAnnotation. The docs clarify this, but still violates the principle of least astonishment.
If cluster size is less than this size, display individual markers.
Similar to MIN_CLUSTER_SIZE in Google's Android Maps Utils library.
Has to work with annotations at the same location (which will still be clustered).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.