如何在 Objective-C 中实现 MKClusterAnnotations?
How to implement MKClusterAnnotations in Objective-C?
我正在尝试为我的 Apple 地图上彼此非常接近的注释创建聚类视图。我知道 Apple 在 iOS 11 中推出了原生集群视图工具包,但我在网上找到的所有教程都是用 Swift 编写的。我希望有人可以教我或向我推荐任何我可以阅读的教程,以了解如何在 Objective-C 中实现集群注释。
我的想法是创建一个ClusterViewclass,继承MKAnnotationViewclass,然后在mapView控制器中创建一个ClusterView的实例。
我看过apple的文档,它只给你提供了我可能需要调用的函数,但没有说明如何使用,这是link给Apple的文档:https://developer.apple.com/documentation/mapkit/mkclusterannotation?language=objc
如有任何帮助,我们将不胜感激!
这是一个简单的几个步骤的基本示例
1) 在 viewDidLoad
中添加以下注释就可以了
MKPointAnnotation *point1 = [[MKPointAnnotation alloc] init];
CLLocationCoordinate2D c1;
c1.latitude = 46.469391;
c1.longitude = 30.740883;
point1.coordinate = c1;
point1.title = @"Minsk, Belarus";
[self.mapView addAnnotation:point1];
MKPointAnnotation *point2 = [[MKPointAnnotation alloc] init];
CLLocationCoordinate2D c2;
c2.latitude = 46.469391;
c2.longitude = 30.740883;
point2.coordinate = c2;
point2.title = @"Odessa, Ukraine";
[self.mapView addAnnotation:point2];
2) 在 MKMapViewDelegate 的 mapView:viewForAnnotation 中为注释提供可重用的视图,如下所示:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKPointAnnotation class]]) {
MKMarkerAnnotationView* annotationView = (MKMarkerAnnotationView *) (MKMarkerAnnotationView *)[_mapView dequeueReusableAnnotationViewWithIdentifier:@"Jacky.S"];
if (annotationView == nil) {
annotationView = [[MKMarkerAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"Jacky.S"];
annotationView.enabled = YES;
annotationView.clusteringIdentifier = @"pins";
// annotationView.glyphImage = [UIImage imageNamed:@"we can use a nice image instead of the default pins"];
} else {
annotationView.annotation = annotation;
annotationView.clusteringIdentifier = @"pins";
}
return annotationView;
}
return nil;
}
不要忘记将 MKMapViewDelegate
设置为 UIViewController
[self.mapView setDelegate:self];
更新 刚发完一个 gist 展示了如何继承 MKMarkerAnnotationView
基本步骤如下:
定义注释视图,指定 clusteringIdentifier
和 collisionMode
:
// CustomAnnotationView.h
@import MapKit;
@interface CustomAnnotationView : MKMarkerAnnotationView
@end
和
// 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
可选,如果需要,您可以定义自己的集群注释视图,指定 displayPriority
和 collisionMode
。这个也更新集群的图像以指示有多少注释被集群:
// ClusterAnnotationView.h
@import MapKit;
@interface ClusterAnnotationView : MKAnnotationView
@end
和
// 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
如果你不想的话,你不必为集群创建自己的子类。但这只是说明了如何完全控制集群的外观,如果您选择这样做的话。
然后你的视图控制器只需要注册合适的类就大功告成了(不需要地图视图委托):
[self.mapView registerClass:[CustomAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultAnnotationViewReuseIdentifier];
如果您想使用自定义聚类视图,您也可以注册它:
[self.mapView registerClass:[ClusterAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultClusterAnnotationViewReuseIdentifier];
例如:
// 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
产生:
我正在尝试为我的 Apple 地图上彼此非常接近的注释创建聚类视图。我知道 Apple 在 iOS 11 中推出了原生集群视图工具包,但我在网上找到的所有教程都是用 Swift 编写的。我希望有人可以教我或向我推荐任何我可以阅读的教程,以了解如何在 Objective-C 中实现集群注释。
我的想法是创建一个ClusterViewclass,继承MKAnnotationViewclass,然后在mapView控制器中创建一个ClusterView的实例。
我看过apple的文档,它只给你提供了我可能需要调用的函数,但没有说明如何使用,这是link给Apple的文档:https://developer.apple.com/documentation/mapkit/mkclusterannotation?language=objc
如有任何帮助,我们将不胜感激!
这是一个简单的几个步骤的基本示例
1) 在 viewDidLoad
中添加以下注释就可以了
MKPointAnnotation *point1 = [[MKPointAnnotation alloc] init];
CLLocationCoordinate2D c1;
c1.latitude = 46.469391;
c1.longitude = 30.740883;
point1.coordinate = c1;
point1.title = @"Minsk, Belarus";
[self.mapView addAnnotation:point1];
MKPointAnnotation *point2 = [[MKPointAnnotation alloc] init];
CLLocationCoordinate2D c2;
c2.latitude = 46.469391;
c2.longitude = 30.740883;
point2.coordinate = c2;
point2.title = @"Odessa, Ukraine";
[self.mapView addAnnotation:point2];
2) 在 MKMapViewDelegate 的 mapView:viewForAnnotation 中为注释提供可重用的视图,如下所示:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKPointAnnotation class]]) {
MKMarkerAnnotationView* annotationView = (MKMarkerAnnotationView *) (MKMarkerAnnotationView *)[_mapView dequeueReusableAnnotationViewWithIdentifier:@"Jacky.S"];
if (annotationView == nil) {
annotationView = [[MKMarkerAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"Jacky.S"];
annotationView.enabled = YES;
annotationView.clusteringIdentifier = @"pins";
// annotationView.glyphImage = [UIImage imageNamed:@"we can use a nice image instead of the default pins"];
} else {
annotationView.annotation = annotation;
annotationView.clusteringIdentifier = @"pins";
}
return annotationView;
}
return nil;
}
不要忘记将 MKMapViewDelegate
设置为 UIViewController
[self.mapView setDelegate:self];
更新 刚发完一个 gist 展示了如何继承 MKMarkerAnnotationView
基本步骤如下:
定义注释视图,指定
clusteringIdentifier
和collisionMode
:// CustomAnnotationView.h @import MapKit; @interface CustomAnnotationView : MKMarkerAnnotationView @end
和
// 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
可选,如果需要,您可以定义自己的集群注释视图,指定
displayPriority
和collisionMode
。这个也更新集群的图像以指示有多少注释被集群:// ClusterAnnotationView.h @import MapKit; @interface ClusterAnnotationView : MKAnnotationView @end
和
// 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
如果你不想的话,你不必为集群创建自己的子类。但这只是说明了如何完全控制集群的外观,如果您选择这样做的话。
然后你的视图控制器只需要注册合适的类就大功告成了(不需要地图视图委托):
[self.mapView registerClass:[CustomAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultAnnotationViewReuseIdentifier];
如果您想使用自定义聚类视图,您也可以注册它:
[self.mapView registerClass:[ClusterAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultClusterAnnotationViewReuseIdentifier];
例如:
// 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
产生: