13

MKAnnotationView をクリックすると視覚化される iOS8 MapView Callout バブルをカスタマイズしたいと考えています。デフォルトの吹き出しは少し制限がある (タイトル、サブタイトル、および 2 つのアクセサリ ビューしかない) ため、別の解決策を見つけるのに苦労しています。ここに2つの可能な方法と私が直面している相対的な問題があります:

問題 1) カスタム吹き出しの作成

Appleのドキュメントを掘り下げると、これが見つかりました:

標準の吹き出しの代わりにカスタム ビューを使用する場合は、ユーザーが吹き出しを操作したときに吹き出しが適切に表示および非表示になるように、追加の作業を行う必要があります。次の手順は、ボタンを含むカスタム コールアウトを作成するプロセスの概要を示しています。

カスタム コールアウトを表す NSView または UIView サブクラスを設計します。カスタム コンテンツを描画するには、サブクラスで drawRect: メソッドを実装する必要がある可能性があります。吹き出しビューを初期化し、ボタンに関連するアクションを実行するビュー コントローラーを作成します。リスト 6-7 に示すように、アノテーション ビューで、hitTest: を実装して、アノテーション ビューの境界外でコールアウト ビューの境界内にあるヒットに応答します。注釈ビューで setSelected:animated: を実装して、ユーザーがクリックまたはタップしたときに注釈ビューのサブビューとして吹き出しビューを追加します。ユーザーがコールアウト ビューを選択したときにコールアウト ビューがすでに表示されている場合、 setSelected: メソッドは注釈ビューからコールアウト サブビューを削除する必要があります (リスト 6-8 を参照)。注釈ビューの initWithAnnotation: メソッドで、canShowCallout プロパティを NO に設定して、ユーザーが注釈を選択したときにマップに標準の吹き出しが表示されないようにします。リスト 6-7 は、hitTest を実装する例を示しています。これは、注釈ビューの境界外にある可能性のあるコールアウト ビューでヒットを処理します。

Listing 6-7  Responding to hits within a custom callout
- (NSView *)hitTest:(NSPoint)point
{
    NSView *hitView = [super hitTest:point];
    if (hitView == nil && self.selected) {
        NSPoint pointInAnnotationView = [self.superview convertPoint:point toView:self];
        NSView *calloutView = self.calloutViewController.view;
        hitView = [calloutView hitTest:pointInAnnotationView];
    }
    return hitView;
}

リスト 6-8 は、 setSelected:animated: を実装して、ユーザーが注釈ビューを選択したときにカスタム コールアウト ビューの到着と終了をアニメーション化する例を示しています。

Listing 6-8  Adding and removing a custom callout view
- (void)setSelected:(BOOL)selected
{
    [super setSelected:selected];

    // Get the custom callout view.
    NSView *calloutView = self.calloutViewController.view;
    if (selected) {
        NSRect annotationViewBounds = self.bounds;
        NSRect calloutViewFrame = calloutView.frame;
      // Center the callout view above and to the right of the annotation view.
        calloutViewFrame.origin.x = -(NSWidth(calloutViewFrame) - NSWidth(annotationViewBounds)) * 0.5;
        calloutViewFrame.origin.y = -NSHeight(calloutViewFrame) + 15.0;
        calloutView.frame = calloutViewFrame;

        [self addSubview:calloutView];
    } else {
        [calloutView.animator removeFromSuperview];
    }
}

さて、この Objective-C コードを Swift に変換しようとすると、次のプロパティが見つかりません。

NSView *calloutView = self.calloutViewController.view;

吹き出しの吹き出しビューにアクセスするにはどうすればよいですか?

問題 2) デフォルトの吹き出しの変更

前に述べたように、表示されるデフォルトのコールアウトには、タイトル、サブタイトル、および 2 つのアクセサリ ビューがあります。文字列のフォント スタイルやバブルの色をあまり変更できないことに気付きました。また、タイトルが 24 文字を超えると、アクセサリ ビューの位置がめちゃくちゃになります。この問題を回避するにはどうすればよいですか?

4

1 に答える 1

19

calloutViewController は、イベントを処理するためのカスタム コールアウト ビューの一部です。MapKit や他の場所にはありません。
リンゴの指示は良いです。独自のコールアウトを作成するには、次の手順に従う必要があります。

1. Create custom MKAnnotationView or MAPinAnnotationView
2. Override setSelected and hitTest methods in your annotation
3. Create your own callout view
4. Override hitTest and pointInside in you callout view
5. Implement MapView delegate methods didSelectAnnotationView, didDeselectAnnotationView

選択を失うことなく吹き出しビュー内でタッチを処理できるこれらのソリューションに行き着きました。

注釈

class MapPin: MKAnnotationView {
    class var reuseIdentifier:String {
        return "mapPin"
    }

    private var calloutView:MapPinCallout?
    private var hitOutside:Bool = true

    var preventDeselection:Bool {
        return !hitOutside
    }


    convenience init(annotation:MKAnnotation!) {
        self.init(annotation: annotation, reuseIdentifier: MapPin.reuseIdentifier)

        canShowCallout = false;
    }

    override func setSelected(selected: Bool, animated: Bool) {
        let calloutViewAdded = calloutView?.superview != nil

        if (selected || !selected && hitOutside) {
            super.setSelected(selected, animated: animated)
        }

        self.superview?.bringSubviewToFront(self)

        if (calloutView == nil) {
            calloutView = MapPinCallout()
        }

        if (self.selected && !calloutViewAdded) {
            addSubview(calloutView!)
        }

        if (!self.selected) {
            calloutView?.removeFromSuperview()
        }
    }

    override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        var hitView = super.hitTest(point, withEvent: event)

        if let callout = calloutView {
            if (hitView == nil && self.selected) {
                hitView = callout.hitTest(point, withEvent: event)
            }
        }

        hitOutside = hitView == nil

        return hitView;
    }
}

吹き出しビュー

class MapPinCallout: UIView {
    override func hitTest(var point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        let viewPoint = superview?.convertPoint(point, toView: self) ?? point

        let isInsideView = pointInside(viewPoint, withEvent: event)

        var view = super.hitTest(viewPoint, withEvent: event)

        return view
    }

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        return CGRectContainsPoint(bounds, point)
    }
}

何か他のものが必要であるが、コールアウトでボタンが反応する場合は、hitTest がビューを返す前に、特定のビューでタッチを処理するコードを追加します。

if calloutState == .Expanded && CGRectContainsPoint(tableView.frame, viewPoint) {
    view = tableView.hitTest(convertPoint(viewPoint, toView: tableView), withEvent: event)
}

デリゲート メソッド

func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!) {
    if let mapPin = view as? MapPin {
        updatePinPosition(mapPin)
    }
}

func mapView(mapView: MKMapView!, didDeselectAnnotationView view: MKAnnotationView!) {
    if let mapPin = view as? MapPin {
        if mapPin.preventDeselection {
            mapView.selectAnnotation(view.annotation, animated: false)
        }
    }
}

func updatePinPosition(pin:MapPin) {
    let defaultShift:CGFloat = 50
    let pinPosition = CGPointMake(pin.frame.midX, pin.frame.maxY)

    let y = pinPosition.y - defaultShift

    let controlPoint = CGPointMake(pinPosition.x, y)
    let controlPointCoordinate = mapView.convertPoint(controlPoint, toCoordinateFromView: mapView)

    mapView.setCenterCoordinate(controlPointCoordinate, animated: true)
}
于 2015-02-12T21:30:40.853 に答える