How to Check If Annotation Is Clustered (Mkmarkerannotationview and Cluster)

How to check if annotation is clustered (MKMarkerAnnotationView and Cluster)

In iOS 11, Apple also introduce a new callback in MKMapViewDelegate:

func mapView(_ mapView: MKMapView, clusterAnnotationForMemberAnnotations memberAnnotations: [MKAnnotation]) -> MKClusterAnnotation

Before annotations become clustered, this function will be called to request a MKClusterAnnotation for memberAnnotations. So the second parameter named memberAnnotations indicates the annotations to be clustered.

EDIT (4/1/2018):

There are two key steps for cluster annotation:

First, before annotations become clustered, MapKit invoke mapView:clusterAnnotationForMemberAnnotations: function to request a MKClusterAnnotation for memberAnnotations.

Secondly, MapKit add the MKClusterAnnotation to MKMapView, and mapView:viewForAnnotation: function will be called to produce a MKAnnotationView.

So you can deselect annotation in either of the two steps, like this:

var selectedAnnotation: MKAnnotation? //the selected annotation

 func mapView(_ mapView: MKMapView, clusterAnnotationForMemberAnnotations memberAnnotations: [MKAnnotation]) -> MKClusterAnnotation {
for annotation in memberAnnotations {
if annotation === selectedAnnotation {
mapView.deselectAnnotation(selectedAnnotation, animated: false)//Or remove the callout
}
}

//...
}

Or:

 func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let clusterAnnotation = annotation as? MKClusterAnnotation {
for annotation in clusterAnnotation.memberAnnotations {
if annotation === selectedAnnotation {
mapView.deselectAnnotation(selectedAnnotation, animated: false)//Or remove the callout
}
}
}

//...
}

check if MKMarkerAnnotationView is cluttered or decluttered in MKMapView

How I can come to know that particular annotation is cluttered and decluttered.

Check within MKClusterAnnotation.memberAnnotations if a MKAnnotation is present, like so:

func isCluttered(annotation: MKAnnotation) -> Bool {
let clusters = mapView.annotations.filter({ $0 is MKClusterAnnotation }) as! [MKClusterAnnotation]
for cluster in clusters {
if cluster.memberAnnotations.first(where: { $0 === annotation }) != nil {
return true
}
}
return false
}

Usage: picks a random annotation from mapView

let annotations = mapView.annotations.filter { $0 is Cycle }
let randomIndex = Int(arc4random_uniform(UInt32(annotations.count)))
if (isCluttered(annotation: annotations[randomIndex])) {
print("Cluttered")
} else {
print("Not cluttered")
mapView.selectAnnotation(annotations[randomIndex], animated: true)
}

check if MKMarkerAnnotationView is cluttered or decluttered in MKMapView

MKMarkerAnnotationView is a subclass of MKAnnotationView, and you can override setSelected(_:animated:), for an example open ClusterAnnotationView.swift from your linked sample code and paste this:

override func setSelected(_ selected: Bool, animated: Bool) {
let cluster = annotation as? MKClusterAnnotation
print("\(selected ? "Selecting" : "Deselected") Clustered Annotation \(cluster?.memberAnnotations.count ?? -1)")
}

Similarly you can override the setSelected(_:animated:) method in each MKMarkerAnnotationView in CycleAnnotationView.swift, paste in all 3 classes:

override func setSelected(_ selected: Bool, animated: Bool) {
print("\(selected ? "Selecting" : "Deselected") unclustered annotation with type: \(clusteringIdentifier!)")
}

Now run and tap annotations on map and check Debug area for print messages.

How to implement MKClusterAnnotations in Objective-C?

Here are the basic steps:

  1. Define your annotation view, specifying clusteringIdentifier and collisionMode:

    //  CustomAnnotationView.h

    @import MapKit;

    @interface CustomAnnotationView : MKMarkerAnnotationView

    @end

    and

    //  CustomAnnotationView.m

    #import "CustomAnnotationView.h"

    static NSString *identifier = @"com.domain.clusteringIdentifier";

    @implementation CustomAnnotationView

    - (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
    if ((self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier])) {
    self.clusteringIdentifier = identifier;
    self.collisionMode = MKAnnotationViewCollisionModeCircle;
    }

    return self;
    }

    - (void)setAnnotation:(id<MKAnnotation>)annotation {
    [super setAnnotation:annotation];

    self.clusteringIdentifier = identifier;
    }

    @end
  2. Optionally, if you want, you can define your own cluster annotation view, specifying displayPriority and collisionMode. This one also updates the image for the cluster to indicate how many annotations are clustered:

    //  ClusterAnnotationView.h

    @import MapKit;

    @interface ClusterAnnotationView : MKAnnotationView

    @end

    and

    //  ClusterAnnotationView.m

    #import "ClusterAnnotationView.h"

    @implementation ClusterAnnotationView

    - (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
    if ((self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier])) {
    self.displayPriority = MKFeatureDisplayPriorityDefaultHigh;
    self.collisionMode = MKAnnotationViewCollisionModeCircle;
    }

    return self;
    }

    - (void)setAnnotation:(id<MKAnnotation>)annotation {
    super.annotation = annotation;
    [self updateImage:annotation];
    }

    - (void)updateImage:(MKClusterAnnotation *)cluster {
    if (!cluster) {
    self.image = nil;
    return;
    }

    CGRect rect = CGRectMake(0, 0, 40, 40);
    UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:rect.size];
    self.image = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
    // circle

    [[UIColor blueColor] setFill];
    [[UIColor whiteColor] setStroke];

    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect];
    path.lineWidth = 0.5;
    [path fill];
    [path stroke];

    // count

    NSString *text = [NSString stringWithFormat:@"%ld", (long) cluster.memberAnnotations.count];
    NSDictionary<NSAttributedStringKey, id> *attributes = @{
    NSFontAttributeName: [UIFont preferredFontForTextStyle: UIFontTextStyleBody],
    NSForegroundColorAttributeName: [UIColor whiteColor]
    };
    CGSize size = [text sizeWithAttributes:attributes];
    CGRect textRect = CGRectMake(rect.origin.x + (rect.size.width - size.width) / 2,
    rect.origin.y + (rect.size.height - size.height) / 2,
    size.width,
    size.height);
    [text drawInRect:textRect withAttributes:attributes];
    }];
    }

    @end

    You don’t have to create your own subclass for the cluster if you don’t want to. But this just illustrates how you can completely control the appearance of the cluster, should you choose to do so.

  3. Then your view controller just needs to register the appropriate classes and you’re done (no map view delegate needed):

    [self.mapView registerClass:[CustomAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultAnnotationViewReuseIdentifier];

    If you want to use your custom clustering view, you can register that, too:

    [self.mapView registerClass:[ClusterAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultClusterAnnotationViewReuseIdentifier];

    For example:

    //  ViewController.m

    #import “ViewController.h"

    @import MapKit;

    #import "CustomAnnotationView.h"
    #import "ClusterAnnotationView.h"

    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet MKMapView *mapView;
    @end

    @implementation ViewController

    - (void)viewDidLoad {
    [super viewDidLoad];

    [self configureMapView];
    }

    - (void)configureMapView {
    self.mapView.userTrackingMode = MKUserTrackingModeFollow;

    [self.mapView registerClass:[CustomAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultAnnotationViewReuseIdentifier];
    [self.mapView registerClass:[ClusterAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultClusterAnnotationViewReuseIdentifier];
    }

    // I’m going to search for restaurants and add annotations for those,
    // but do whatever you want

    - (void)performSearch {
    MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init];
    request.naturalLanguageQuery = @"restaurant";
    request.region = self.mapView.region;

    MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request];
    [search startWithCompletionHandler:^(MKLocalSearchResponse * _Nullable response, NSError * _Nullable error) {
    if (error) {
    NSLog(@"%@", error);
    return;
    }

    for (MKMapItem *mapItem in response.mapItems) {
    MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
    annotation.coordinate = mapItem.placemark.coordinate;
    annotation.title = mapItem.name;
    annotation.subtitle = mapItem.placemark.thoroughfare;
    [self.mapView addAnnotation:annotation];
    }
    }];
    }

    @end

That yields:

demo



Related Topics



Leave a reply



Submit