如何在 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

基本步骤如下:

  1. 定义注释视图,指定 clusteringIdentifiercollisionMode:

    //  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
    
  2. 可选,如果需要,您可以定义自己的集群注释视图,指定 displayPrioritycollisionMode。这个也更新集群的图像以指示有多少注释被集群:

    //  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
    

    如果你不想的话,你不必为集群创建自己的子类。但这只是说明了如何完全控制集群的外观,如果您选择这样做的话。

  3. 然后你的视图控制器只需要注册合适的类就大功告成了(不需要地图视图委托):

    [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
    

产生: