3

MKAnnotationViewのカスタムサブクラスを使用します。mapView :didSelectAnnotationView: Mapのデリゲートのメソッドで、このクラスのメソッドを呼び出します。このクラスは、画像をサブビューとしてUIImageViewに追加します。これは、カスタムアノテーションの呼び出しとして機能します。

デフォルトのMKPinAnnotationViewマップを使用すると、マップ領域が自動的に調整され、表示されたばかりの注釈コールアウトが表示されます。カスタムMKAnnotationViewサブクラスを使用してこの動作を実装するにはどうすればよいですか?

4

2 に答える 2

6

現在のソリューション

以下で説明するものを実装したデモプロジェクトを作成しました 。AdjustRegionToFitAnnotationCalloutプロジェクトを参照してください。

Map KitのMKMapViewがマップ注釈をレンダリングする方法の最新のiOS7の変更により、この問題を再検討することになりました。私はそれについてより正確に考え、はるかに優れた解決策を考え出しました。前の解決策をこの回答の最後に残しておきますが、覚えておいてください-そのようにしたとき、私はとても間違っていました。

まず最初にCGRectTransformToContainRect()、特定のを拡張CGRectして別のを含むヘルパーが必要になりますCGRect

注:動作は動作とは異なります。両方を含む最小値を返しますCGRectUnion()が、次のヘルパーは平行移動を許可します。つまり、等しいが、そうではありません。この動作は、平行移動を使用して調整するだけで、マップのズームを避けたい場合に必要なものです。CGRectUnion()CGRectCGRectsCGRectTransformToContainRect(CGRectMake(0, 0, 100, 100), CGRectMake(50, 50, 100, 100))(CGRect){50, 50, 100, 100}(CGRect){0, 0, 150, 150}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クラスの注釈を選択すると、そのcalloutViewがサブビューとして追加されるため、カスタム注釈の呼び出しビューが表示されます。これは、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/MapKitDataTypesReference/Reference/reference.html

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

また、WWDC 2013セッション「マップキットの新機能」(#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は領域を定義する方法ですが、代わりにそのエッジの座標を使用します。

MKEdgedRegionFromCoordinateRegion()は、一目瞭然のコンバーターメソッドです。

サブビューとしてのコールアウトを含む、アノテーション用の次のクラスがあるとします。

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

マップ上でAnnotationViewクラスの注釈を選択すると、そのcalloutViewがサブビューとして追加されるため、カスタム注釈の呼び出しビューが表示されます。これは、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;
    }
}
于 2013-01-11T03:03:11.550 に答える
0

画面の表示されている長方形にルートとコールアウトを合わせるために、同様の解決策を探していました。私はいくつかの解決策を試しましたが、最終的には十分なパディングを設定するだけで終わりましたsetVisibleMapRect:edgePadding:animated:。それほど洗練されていないかもしれませんが、基本的に私が必要なことをします。

MKMapRect routeMapRect = myRoute.polyline.boundingMapRect;
CGFloat padding = myCallout.bounds.width / 2.0;
[myMapView setVisibleMapRect: routeMapRect edgePadding:UIEdgeInsetsMake(padding, padding, padding, padding) animated:YES];

もちろん、これははるかに最適化できます。たとえば、実際にパディングが必要な側を検出し、反対側に小さいパディングを設定します。しかし、あなたはその考えを理解します。

于 2016-01-15T09:49:11.757 に答える