如何调整区域以适应刚刚出现的自定义注释标注?

2024-02-23

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

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


目前的解决方案

我已经制作了演示项目,实现了下面讨论的内容:请参阅那里调整区域以适合注释标注 https://github.com/stanislaw/Examples/tree/20140114-adjust-region-to-fit-annotation-callout项目。

最新的iOS7对Map Kit的MKMapView渲染地图标注方式的改变让我重新审视这个问题。我对此进行了更准确的思考,并提出了更好的解决方案。我将把之前的解决方案留在这个答案的底部,但请记住 - 当我这样做时我错了。

首先我们需要一个帮手CGRectTransformToContainRect()扩展给定的CGRect包含另一个CGRect.

注意:它的行为与CGRectUnion() does - CGRectUnion()仅返回最小的CGRect包含两者CGRects,而以下助手允许平行移动,即CGRectTransformToContainRect(CGRectMake(0, 0, 100, 100), CGRectMake(50, 50, 100, 100)) equals (CGRect){50, 50, 100, 100}并不是(CGRect){0, 0, 150, 150} like CGRectUnion()可以。当我们想要时,这种行为正是我们所需要的仅使用平行移动进行调整并希望避免地图缩放.

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 类注释时,它会添加其标注视图作为子视图,因此显示自定义注释标注视图。这是使用 MKMapViewDelegate 的方法完成的:

- (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:方法。现在我对当前的解决方案以及我对这个(和一些相关)问题的理解非常满意。如果您对上述解决方案有任何意见,请随时与我联系(请参阅简介)。

以下 Apple 文档对于了解 -[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/MKMapView_Class/MKMapView/MKMapView.html

http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MapKit 数据类型参考/Reference/reference.html http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MapKitDataTypesReference/Reference/reference.html

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

此外,WWDC 2013 会议“Map Kit 的新增功能”(#304) 的前 10-15 分钟也非常值得观看,因为可以快速演示由 Apple 工程师完成的整个“带注释的地图”设置。


初步解决方案(在iOS7下不起作用,不要使用它,而是使用上面的解决方案)

不知怎的,我有一次忘记回答我的问题。这是我现在使用的完整解决方案(为了可读性稍作编辑):

首先,将一些地图逻辑封装在帮助程序文件中,例如 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 只是定义区域的一种方法,但使用其边缘的坐标。

MKEdgedRegionFromCooperativeRegion() 是一个不言自明的转换器方法。

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

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

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

- (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;
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何调整区域以适应刚刚出现的自定义注释标注? 的相关文章

  • React Native:不透明视图内的透明视图

    我想用不透明框架和透明中心显示相机的视图 就像图片中的一样 黑色部分是相机的视图 我正在寻找具有纯反应本机组件的解决方案 没有额外的库 例如https github com gilbox react native masked view h
  • 将子视图控制器的视图添加到父视图控制器的子视图

    我想添加一个表视图控制器作为容器视图控制器的子视图控制器 如下所示 根据苹果公司的查看控制器编程指南 http developer apple com library ios featuredarticles ViewControllerP
  • 多次添加同一个子视图来查看

    我不知道这是否可行 但我想做的是将子视图多次添加到视图中 我尝试过这样的事情 self view addSubview newView newView center CGPointMake 160 100 self view addSubv
  • 无法将“SDWebImageActivityIndi​​cator”类型的值分配给“ST_SDWebImageIndicator”类型?

    I have multiple flavours targets in my Xcode project I am also using SDWebImage in my app Everything was working fine un
  • 防止UIScrollView的UIPanGestureRecognizer遮挡UIScreenEdgePanGestureRecognizer

    我有一个UIScrollView它填满了我应用程序的一页上的屏幕 但我希望允许用户从屏幕边缘平移以显示其后面的视图 问题是 UIScrollView 窃取了我的触摸UIScreenEdgePanGestureRecognizer在屏幕边缘
  • iOS - NSNotificationCenter 多个UIKeyboard通知

    我有两个视图控制器 我们称它们为 A 和 B 1 在 A 中 我显示一个包含文本字段的 popOver 2 B中有一个UITextView用于简单的文本编辑 我必须管理 A 和 B 中的键盘才能滚动键盘隐藏的内容 我知道如何重新定位内容 我
  • 将带有地理位置数据的照片保存到照片库 Swift 3

    如何使用地理位置元数据将照片保存到照片库 我已请求 并允许 应用程序访问用户位置 private func allowAccessToUserLocation locationManager CLLocationManager locati
  • 如何在 Apple Watch Extension/App 和 iOS App 之间建立通信通道

    我正在探索 WatchKit SDK 当我有 WatchKit 应用程序时 是否可以在 WatchKit 应用程序上从 iPhone 应用程序设置值 例如文本 设置 我可以从 iPhone 应用程序调用 WatchKit 应用程序扩展中的函
  • Swift - 选择值后隐藏 pickerView

    我发现了类似的问题 他们的答案很有帮助 但我坚持最后一件事 我试图在点击字段时显示 pickerView 然后选择数据时 我希望 pickerView 隐藏 我可以从 pickerView 获取数据来隐藏 但是 pickerView 后面仍
  • iOS中的performSelector有什么用

    的作用是什么执行选择器 比较 self btnClicked and self performSelector selector btnClicked void btnClicked NSLog Method Called 两者都对我来说工
  • Objective Flickr 照片上传错误

    我正在使用 ObjectiveFlickr 库将照片从我的 iPhone 应用程序上传到 Flickr 我可以授权该应用程序并执行一般请求 但在尝试上传照片时遇到错误 要上传的照片是使用 AVFoundation 捕获的图像 这是相关代码
  • UICollectionView 未出现

    我正在尝试设置UICollectionView 以编程方式在我的视图控制器中扩展UIViewController 由于某种原因 我的收藏视图根本没有显示 以下是我所拥有的 为什么没有出现 我将它连接到委托和数据源并将其添加为子视图self
  • 如何请求用户开启定位服务

    我需要我的应用程序来访问用户的当前位置 它在应用程序开始时检查用户是否已设置 如果没有 我需要应用程序显示提示以使其使用位置服务 就像警报视图一样 点击按钮 它应该会带您进入 iPhone 上的位置服务屏幕 您可以通过以下代码检查 loca
  • iOS UIButton 带有圆角和背景 bug

    我发现圆形 UIButton 存在一个奇怪的问题 这是我创建此按钮的代码块 let roundedButton UIButton type System roundedButton frame CGRectMake 100 100 100
  • 使用 Google place API 从 lat long 获取附近的地点

    我正在使用 google place API 即 https maps googleapis com maps api place search json location 33 7167 73 0667 radius 500 type f
  • SpriteKitPhysicsBody非矩形碰撞

    pipeUp physicsBody SKPhysicsBody rectangleOfSize pipeUp size 在此编码中我使用了rectangleOfSize对于碰撞物理体 但如果我想按像素仅使用图像的形状 我应该使用什么而不是
  • 检查 touchend 是否在拖动后出现

    我有一些代码可以更改表的类 在手机上 有时表格对于屏幕来说太宽 用户将拖动 滚动来查看内容 但是 当他们触摸并拖动表格时 每次拖动都会触发 touchend 如何测试触摸端是否是触摸拖动的结果 我尝试跟踪dragstart和dragend
  • 如何更改已上传的 Firebase 存储图像文件名?

    我需要更改已上传到 firebase 存储中的文件名 因为 在 firebase 存储中上传图像后 我将 url 保存在 firebase 数据库中的特定子 文件夹 下 但是 当我将图像移动到另一个子 文件夹 时 我需要根据新名称更改存储中
  • 自定义 MKAnnotationView - 如何捕获触摸而不忽略标注?

    我有一个自定义 MKAnnotationView 子类 它完全按照我想要的方式显示视图 在那个视图中 我有一个按钮 我想捕获按钮上的事件来执行操作 这很好用 但是 我不希望标注被忽略或消失 基本上 触摸标注中的按钮将开始播放声音 但我想保留
  • NSPredicate 的 onFormat 字符串

    我想用 id 键对数据进行排序 我如何理解格式字符串的用途NSPredicate格式 我有一个100号的帖子 我的代码 let objectIDs posts map 0 id let predicate NSPredicate forma

随机推荐