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 inMKMapView
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:
Define your annotation view, specifying
clusteringIdentifier
andcollisionMode
:// CustomAnnotationView.h
@import MapKit;
@interface CustomAnnotationView : MKMarkerAnnotationView
@endand
// 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;
}
@endOptionally, if you want, you can define your own cluster annotation view, specifying
displayPriority
andcollisionMode
. This one also updates the image for the cluster to indicate how many annotations are clustered:// ClusterAnnotationView.h
@import MapKit;
@interface ClusterAnnotationView : MKAnnotationView
@endand
// 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];
}];
}
@endYou 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.
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:
Related Topics
How to Enable Swift Support for Existing Project in Flutter
Conflicting Definition of Swift Struct and Array
Swift .Uppercasestring or .Lowercasestring Property Replacement
Codable and Xmlparser in Swift
Using Foreach with a an Array of Bindings (Swiftui)
Optionals VS Throwing Functions
Nspredicate with Swift and Core Data
How to Check If a String Contains Letters in Swift
Swift: Uicollectionview Selecting Cell Indexpath Issues
How to Add "%" to Data in iOS-Chart
How to Fix Error: This Class Is Not Key Value Coding-Compliant for the Key Tableview.'
Swiftui MACos Xcode Style Toolbar
Convert String to Cgfloat in Swift
Scroll Uicollectionview to Bottom
How to Fix ' *Pod* Does Not Support Provisioning Profiles' in Azure Devops Build Agent