如何调整区域以适应刚刚出现的自定义注释标注? [英] How to adjust region to fit custom annotation callout that have just appeared?

查看:105
本文介绍了如何调整区域以适应刚刚出现的自定义注释标注?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用我的MKAnnotationView的自定义子类.在地图委托的 mapView:didSelectAnnotationView:方法中,我调用此类的方法,该方法将UIImageView带有图像作为子视图-它用作我的自定义注释标注.

I use my custom subclass of MKAnnotationView. In mapView:didSelectAnnotationView: method of my Map's delegate I call the method of this class, which adds UIImageView with an image as a subview - it serves as my custom annotation callout.

使用默认的MKPinAnnotationView时,地图会自动调整地图区域以显示刚刚出现的注释标注.如何使用自定义MKAnnotationView子类来实现此行为?

When using default MKPinAnnotationView map does automatically adjust map region to display the annotation callout that have just appeared. How can I implement this behavior using custom MKAnnotationView subclass?

推荐答案

当前解决方案

我精心制作了演示项目,实现了下面讨论的内容:请参见

I've crafted demo project having the stuff discussed below implemented: see there AdjustRegionToFitAnnotationCallout project.

关于Map Kit的MKMapView渲染地图注释的方式的最新iOS7更改使我重新审视了此问题.我对此进行了更准确的思考,并提出了很多非常好的解决方案.我将先前的解决方案留在该答案的底部,但请记住-这样做时我错了.

The latest iOS7 changes in how Map Kit's MKMapView renders map annotations made me to revisit this problem. I've made more accurate thinking about it and come up with much, very much better solution. I will leave the previous solution at the bottom of this answer, but remember - I was so wrong when I did it that way.

首先,我们将需要一个帮助程序CGRectTransformToContainRect(),该帮助程序将给定的CGRect扩展为包含另一个CGRect.

First of all we will need a helper CGRectTransformToContainRect() that expands a given CGRect to contain another CGRect.

注意:它的行为不同于CGRectUnion()的行为-CGRectUnion()仅返回包含两个CGRects的最小的CGRect,而以下助手允许平行移动,即CGRectTransformToContainRect(CGRectMake(0, 0, 100, 100), CGRectMake(50, 50, 100, 100))等于(CGRect){50, 50, 100, 100}而不是(CGRect){0, 0, 150, 150}就像CGRectUnion()一样.当我们要仅使用平行移动进行调整并希望避免地图缩放时,正是这种行为.

Note: it's behavior is different from what CGRectUnion() does - CGRectUnion() returns just the smallest CGRect containing both CGRects, whereas the following helper allows parallel movement i.e. CGRectTransformToContainRect(CGRectMake(0, 0, 100, 100), CGRectMake(50, 50, 100, 100)) equals (CGRect){50, 50, 100, 100} and not (CGRect){0, 0, 150, 150} like CGRectUnion() does it. This behavior is exactly what we need when we want to have only adjusts using parallel movements and want to avoid map's zooming.

static inline CGRect CGRectTransformToContainRect(CGRect rectToTransform, CGRect rectToContain) {
    CGFloat diff;
    CGRect transformedRect = rectToTransform;


    // Transformed rect dimensions should encompass the dimensions of both rects
    transformedRect.size.width = MAX(CGRectGetWidth(rectToTransform), CGRectGetWidth(rectToContain));
    transformedRect.size.height = MAX(CGRectGetHeight(rectToTransform), CGRectGetHeight(rectToContain));


    // Comparing max X borders of both rects, adjust if
    if ((diff = CGRectGetMaxX(rectToContain) - CGRectGetMaxX(transformedRect)) > 0) {
        transformedRect.origin.x += diff;
    }
    // Comparing min X borders of both rects, adjust if
    else if ((diff = CGRectGetMinX(transformedRect) - CGRectGetMinX(rectToContain)) > 0) {
        transformedRect.origin.x -= diff;
    }


    // Comparing max Y borders of both rects, adjust if
    if ((diff = CGRectGetMaxY(rectToContain) - CGRectGetMaxY(transformedRect)) > 0) {
        transformedRect.origin.y += diff;
    }
    // Comparing min Y borders of both rects, adjust if
    else if ((diff = CGRectGetMinY(transformedRect) - CGRectGetMinY(rectToContain)) > 0) {
        transformedRect.origin.y -= diff;
    }


    return transformedRect;
}

Adjust method wrapped into an Objective-C category MKMapView(Extensions):

@implementation MKMapView (Extensions)

- (void)adjustToContainRect:(CGRect)rect usingReferenceView:(UIView *)referenceView  animated:(BOOL)animated {
    // I just like this assert here
    NSParameterAssert(referenceView);

    CGRect visibleRect = [self convertRegion:self.region toRectToView:self];

    // We convert our annotation from its own coordinate system to a coodinate system of a map's top view, so we can compare it with the bounds of the map itself
    CGRect annotationRect = [self convertRect:rect fromView:referenceView.superview];

    // Fatten the area occupied by your annotation if you want to have a margin after adjustment
    CGFloat additionalMargin = 2;
    adjustedRect.origin.x -= additionalMargin;
    adjustedRect.origin.y -= additionalMargin;
    adjustedRect.size.width += additionalMargin * 2;
    adjustedRect.size.height += additionalMargin * 2;

    // This is the magic: if the map must expand its bounds to contain annotation, it will do this 
    CGRect adjustedRect = CGRectTransformToContainRect(visibleRect, annotationRect);

    // Now we just convert adjusted rect to a coordinate region
    MKCoordinateRegion adjustedRegion = [self convertRect:adjustedRect toRegionFromView:self];

    // Trivial regionThatFits: sugar and final setRegion:animated: call
    [self setRegion:[self regionThatFits:adjustedRegion] animated:animated];
}

@end

现在控制器和视图:

@interface AnnotationView : MKAnnotationView
@property AnnotationCalloutView *calloutView;
@property (readonly) CGRect annotationViewWithCalloutViewFrame;
@end

@implementation AnnotationView 

- (void)showCalloutBubble {
    // This is a code where you create your custom annotation callout view
    // add add it using -[self addSubview:]
    // At the end of this method a callout view should be displayed.
}

- (CGRect)annotationViewWithCalloutViewFrame {
    // Here you should adjust your annotation frame so it match itself in the moment when annotation callout is displayed and ...

    return CGRectOfAdjustedAnnotation; // ...
}

@end

在地图上选择基于AnnotationView的注释时,它将添加其 calloutView 作为子视图,因此将显示自定义注释标注视图.这是使用MKMapViewDelegate的方法完成的:

When AnnotationView-classed annotation is selected on map, it adds its calloutView as a subview, so custom annotation callout view is displayed. It is done using MKMapViewDelegate's method:

- (void)mapView:(MapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
    // AnnotationPresenter is just a class that contains information to be displayed on callout annotation view
    if ([view.annotation isKindOfClass:[AnnotationPresenter class]]) {
        // Hide another annotation if it is shown
        if (mapView.selectedAnnotationView != nil && [mapView.selectedAnnotationView isKindOfClass:[AnnotationView class]] && mapView.selectedAnnotationView != view) {
            [mapView.selectedAnnotationView hideCalloutBubble];
        }
        mapView.selectedAnnotationView = view;

        annotationView *annotationView = (annotationView *)view;

        // This just adds *calloutView* as a subview    
        [annotationView showCalloutBubble];

        [mapView adjustToContainRect:annotationView.annotationViewWithCalloutViewFrame usingReferenceView:annotationView animated:NO];
    }
}


当然,您的实现可能与我在这里描述的不同(我的是!).上面代码中最重要的部分当然是[MKMapView adjustToContainRect:usingReferenceView:animated:方法.现在,我对当前的解决方案以及对此(以及一些相关)问题的理解非常满意.如果您需要有关上述解决方案的任何意见,请随时与我联系(请参阅个人资料).


Of course your implementation may be different from what I've described here (mine is!). The most important part of above code is of course the [MKMapView adjustToContainRect:usingReferenceView:animated: method. Now I am really satisfied with the current solution and my understanding of this (and some related) problem. If you need any comments about the solution above, feel free to contact me (see profile).

以下Apple文档对于了解-[MKMapView convertRect:fromView:]之类的方法的作用非常有用:

The following Apple docs are very useful to understand what is going on in methods like -[MKMapView convertRect:fromView:]:

http://developer.apple .com/library/ios/#documentation/MapKit/Reference/MKMapView_Class/MKMapView/MKMapView.html

http://developer.apple .com/library/ios/#documentation/MapKit/Reference/MapKitDataTypesReference/Reference/reference.html

http://developer.apple .com/library/ios/#documentation/MapKit/Reference/MapKitFunctionsReference/Reference/reference.html

此外,在WWDC 2013会议地图工具包中的新功能"(#304)的前10-15分钟内,也非常值得观看,它具有由Apple工程师完成的整个带注释的地图"设置的快速演示.

Also the first 10-15 minutes of WWDC 2013 session "What’s New in Map Kit" (#304) are very good to watch to have an excellent quick demo of the whole "Map with annotations" setup done by Apple engineer.

初始解决方案(不适用于iOS7,请勿使用,请改用上述解决方案)

以某种方式,我忘记一次回答我的问题.这是我当今使用的完整解决方案(为便于阅读,对其进行了略微编辑):

Somehow I forgot to answer my question at a time. Here is the complete solution I use nowadays (edited slightly for readability):

首先,将一些映射逻辑封装在诸如MapKit + Helpers.h之类的辅助文件中.

First of all a bit of map logic to be encapsulated somewhere in helpers file like MapKit+Helpers.h

typedef struct {
    CLLocationDegrees top;
    CLLocationDegrees bottom;
} MKLatitudeEdgedSpan;

typedef struct {
    CLLocationDegrees left;
    CLLocationDegrees right;
} MKLongitudeEdgedSpan;

typedef struct {
    MKLatitudeEdgedSpan latitude;
    MKLongitudeEdgedSpan longitude;
} MKEdgedRegion;

MKEdgedRegion MKEdgedRegionFromCoordinateRegion(MKCoordinateRegion region) {
    MKEdgedRegion edgedRegion;

    float latitude = region.center.latitude;
    float longitude = region.center.longitude;
    float latitudeDelta = region.span.latitudeDelta;
    float longitudeDelta = region.span.longitudeDelta;

    edgedRegion.longitude.left = longitude - longitudeDelta / 2;
    edgedRegion.longitude.right = longitude + longitudeDelta / 2;
    edgedRegion.latitude.top = latitude + latitudeDelta / 2;
    edgedRegion.latitude.bottom = latitude - latitudeDelta / 2;

    return edgedRegion;
}

与MKCoordinateRegion(中心坐标+跨度)一样,MKEdgedRegion只是一种定义区域的方法,而是使用其边缘的坐标.

Like MKCoordinateRegion (center coordinate + spans), MKEdgedRegion is just a way to define a region but using coordinates of its edges instead.

MKEdgedRegionFromCoordinateRegion()是一种不言自明的转换器方法.

MKEdgedRegionFromCoordinateRegion() is a self-explanatory converter-method.

假设我们的注释具有以下类,其中包含其标注作为子视图.

Suppose we have the following class for our annotations, containing its callout as a subview.

@interface AnnotationView : MKAnnotationView
@property AnnotationCalloutView *calloutView;
@end

在地图上选择基于AnnotationView的注释时,它将添加其 calloutView 作为子视图,因此将显示自定义注释标注视图.这是使用MKMapViewDelegate的方法完成的:

When AnnotationView-classed annotation is selected on map, it adds its calloutView as a subview, so custom annotation callout view is displayed. It is done using MKMapViewDelegate's method:

- (void)mapView:(MapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
    // AnnotationPresenter is just a class that contains information to be displayed on callout annotation view
    if ([view.annotation isKindOfClass:[AnnotationPresenter class]]) {
        // Hide another annotation if it is shown
        if (mapView.selectedAnnotationView != nil && [mapView.selectedAnnotationView isKindOfClass:[AnnotationView class]] && mapView.selectedAnnotationView != view) {
            [mapView.selectedAnnotationView hideCalloutBubble];
        }
        mapView.selectedAnnotationView = view;

        annotationView *annotationView = (annotationView *)view;

        // This just adds *calloutView* as a subview    
        [annotationView showCalloutBubble];

        /* Here the trickiest piece of code goes */

        /* 1. We capture _annotation's (not callout's)_ frame in its superview's (map's!) coordinate system resulting in something like (CGRect){4910547.000000, 2967852.000000, 23.000000, 28.000000} The .origin.x and .origin.y are especially important! */
        CGRect annotationFrame = annotationView.frame;

        /* 2. Now we need to perform an adjustment, so our frame would correspond to the annotation view's _callout view subview_ that it holds. */
        annotationFrame.origin.x = annotationFrame.origin.x + ANNOTATION_CALLOUT_TRIANLE_HALF; // Mine callout view has small x offset - you should choose yours!
        annotationFrame.origin.y = annotationFrame.origin.y - ANNOTATION_CALLOUT_HEIGHT / 2; // Again my custom offset.
        annotationFrame.size = placeAnnotationView.calloutView.frame.size; // We can grab calloutView size directly because in its case we don't care about the coordinate system.

        MKCoordinateRegion mapRegion = mapView.region;

        /* 3. This was a long run before I did stop to try to pass mapView.view as an argument to _toRegionFromView_. */
        /* annotationView.superView is very important - it gives us the same coordinate system that annotationFrame.origin is based. */
        MKCoordinateRegion annotationRegion = [mapView convertRect:annotationFrame toRegionFromView:annotationView.superview];

        /* I hope that the following MKEdgedRegion magic is self-explanatory */
        MKEdgedRegion mapEdgedRegion = MKEdgedRegionFromCoordinateRegion(mapRegion);
        MKEdgedRegion annotationEdgedRegion = MKEdgedRegionFromCoordinateRegion(annotationRegion);

        float diff;

        if ((diff = (annotationEdgedRegion.longitude.left - mapEdgedRegion.longitude.left)) < 0 ||
            (diff = (annotationEdgedRegion.longitude.right - mapEdgedRegion.longitude.right)) > 0)
            mapRegion.center.longitude += diff;

        if ((diff = (annotationEdgedRegion.latitude.bottom - mapEdgedRegion.latitude.bottom)) < 0 ||
            (diff = (annotationEdgedRegion.latitude.top - mapEdgedRegion.latitude.top)) > 0)
            mapRegion.center.latitude += diff;

        mapView.region = mapRegion;
    }
}

这篇关于如何调整区域以适应刚刚出现的自定义注释标注?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆