Wie kann ich die Callout-Blase für MKAnnotationView anpassen?

88

Ich arbeite derzeit mit dem Mapkit und stecke fest.

Ich habe eine benutzerdefinierte Anmerkungsansicht, die ich verwende, und ich möchte die Bildeigenschaft verwenden, um den Punkt auf der Karte mit meinem eigenen Symbol anzuzeigen. Ich habe das gut funktioniert. Ich möchte aber auch die Standard-Callout-Ansicht überschreiben (die Blase, die mit dem Titel / Untertitel angezeigt wird, wenn das Anmerkungssymbol berührt wird). Ich möchte in der Lage sein, das Callout selbst zu steuern: Das Mapkit bietet nur Zugriff auf die linken und rechten zusätzlichen Callout-Ansichten, aber keine Möglichkeit, eine benutzerdefinierte Ansicht für die Callout-Blase bereitzustellen oder ihr eine Größe von Null oder etwas anderes zuzuweisen.

Meine Idee war es, selectAnnotation / deselectAnnotation in my zu überschreiben MKMapViewDelegateund dann meine eigene benutzerdefinierte Ansicht zu zeichnen, indem ich meine benutzerdefinierte Anmerkungsansicht aufrufe. Dies funktioniert, aber nur dann , wenn canShowCalloutfestgelegt ist YESin meiner benutzerdefinierten Anmerkungsansicht Klasse. Diese Methoden werden NICHT aufgerufen, wenn ich diese Einstellung auf gesetzt habe NO(was ich möchte, damit die Standard-Callout-Blase nicht gezeichnet wird). Ich kann also nicht wissen, ob der Benutzer meinen Punkt auf der Karte berührt (ausgewählt) oder einen Punkt berührt hat, der nicht Teil meiner Anmerkungsansichten ist (gelöscht), ohne dass die Standardansicht der Callout-Blase angezeigt wird.

Ich habe versucht, einen anderen Weg einzuschlagen und alle Berührungsereignisse selbst in der Karte zu behandeln, und ich kann das anscheinend nicht zum Laufen bringen. Ich habe andere Beiträge zum Abfangen von Berührungsereignissen in der Kartenansicht gelesen, aber sie sind nicht genau das, was ich möchte. Gibt es eine Möglichkeit, in die Kartenansicht zu graben, um die Beschriftungsblase vor dem Zeichnen zu entfernen? Ich bin ratlos.

Irgendwelche Vorschläge? Vermisse ich etwas Offensichtliches?

Zach
quelle
Dieser Link funktioniert nicht, hat aber den gleichen Beitrag hier gefunden -> Erstellen von Callouts für benutzerdefinierte Kartenanmerkungen - Teil 1
Fede Mika
2
Sie können sich für eine kurze Demo auf dieses Projekt beziehen . github.com/akshay1188/CustomAnnotation Zusammenstellung der obigen Antworten zu einer laufenden Demo.
Akshay1188

Antworten:

53

Es gibt eine noch einfachere Lösung.

Erstellen Sie eine benutzerdefinierte UIView(für Ihr Callout).

Erstellen Sie dann eine Unterklasse von MKAnnotationViewund überschreiben Sie diese setSelectedwie folgt:

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
    [super setSelected:selected animated:animated];

    if(selected)
    {
        //Add your custom view to self...
    }
    else
    {
        //Remove your custom view...
    }
}

Boom, Arbeit erledigt.

TappCandy
quelle
8
Hallo TappCandy! Zunächst einmal vielen Dank für Ihre Lösung. Es funktioniert, aber wenn eine Ansicht geladen wird (ich mache es aus einer NIB-Datei), funktionieren die Schaltflächen nicht. Was kann ich tun, damit sie richtig funktionieren? Vielen Dank
Frade
Lieben Sie die Einfachheit dieser Lösung!
Eric Brotto
6
Hmm - das geht nicht! Es ersetzt die Kartenanmerkung (dh die Stecknadel) NICHT die Beschriftung "Blase".
Papillon
2
Das wird nicht funktionieren. Die MKAnnotationView enthält keinen Verweis auf die Kartenansicht, sodass beim Hinzufügen eines benutzerdefinierten Callouts mehrere Probleme auftreten. Sie wissen beispielsweise nicht, ob das Hinzufügen als Unteransicht außerhalb des Bildschirms erfolgt.
Cameron Lowell Palmer
@PapillonUK Sie haben die Kontrolle über den Rahmen des Callouts. Es ersetzt nicht den Stift, es erscheint darüber. Passen Sie die Position an, wenn Sie sie als Unteransicht hinzufügen.
Ben Packard
40

detailCalloutAccessoryView

In den alten Tagen war dies ein Schmerz, aber Apple hat es gelöst. Überprüfen Sie einfach die Dokumente auf MKAnnotationView

view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
view.detailCalloutAccessoryView = UIImageView(image: UIImage(named: "zebra"))

Wirklich, das war's. Nimmt jede UIView.

Cameron Lowell Palmer
quelle
Welches ist am besten zu verwenden?
Satyam
13

Wenn Sie von @ TappCandys genial einfacher Antwort fortfahren und Ihre Blase auf die gleiche Weise wie die Standardblase animieren möchten, habe ich diese Animationsmethode erstellt:

- (void)animateIn
{   
    float myBubbleWidth = 247;
    float myBubbleHeight = 59;

    calloutView.frame = CGRectMake(-myBubbleWidth*0.005+8, -myBubbleHeight*0.01-2, myBubbleWidth*0.01, myBubbleHeight*0.01);
    [self addSubview:calloutView];

    [UIView animateWithDuration:0.12 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^(void) {
        calloutView.frame = CGRectMake(-myBubbleWidth*0.55+8, -myBubbleHeight*1.1-2, myBubbleWidth*1.1, myBubbleHeight*1.1);
    } completion:^(BOOL finished) {
        [UIView animateWithDuration:0.1 animations:^(void) {
            calloutView.frame = CGRectMake(-myBubbleWidth*0.475+8, -myBubbleHeight*0.95-2, myBubbleWidth*0.95, myBubbleHeight*0.95);
        } completion:^(BOOL finished) {
            [UIView animateWithDuration:0.075 animations:^(void) {
                calloutView.frame = CGRectMake(-round(myBubbleWidth/2-8), -myBubbleHeight-2, myBubbleWidth, myBubbleHeight);
            }];
        }];
    }];
}

Es sieht ziemlich kompliziert aus, aber solange der Punkt Ihrer Callout-Blase in der Mitte unten liegt, sollten Sie nur in der Lage sein, sie zu ersetzen myBubbleWidthund myBubbleHeightmit Ihrer eigenen Größe zu versehen, damit sie funktioniert. Denken Sie auch daran, dass für Ihre Unteransichten die autoResizeMaskEigenschaft 63 (dh "alle") festgelegt ist, damit sie in der Animation korrekt skaliert werden.

: -Joe

Jowie
quelle
Übrigens können Sie die Skalierungseigenschaft und nicht den Rahmen animieren, um eine ordnungsgemäße Skalierung des Inhalts zu erhalten.
Occulus
Verwenden Sie CGRectMake nicht. Verwenden Sie CGTransform.
Cameron Lowell Palmer
@occulus - Ich glaube, ich habe das zuerst versucht, hatte aber Rendering-Probleme in iOS 4. Aber das könnte daran liegen, dass ich nicht verwendet habe CABasicAnimation... Zu lange her, um mich zu erinnern! Ich weiß, dass ich aufgrund des Mittelversatzes auch die y-Eigenschaft animieren müsste.
Jowie
@CameronLowellPalmer - warum nicht verwenden CGRectMake?
Jowie
@jowie Weil die Syntax besser ist und die magischen Werte in Ihrem Code vermeidet. CGAffineTransformMakeScale (1.1f, 1.1f); Es ist klar, die Box um 110% zu vergrößern. Die Art und Weise, wie Sie es gemacht haben, mag funktionieren, aber es ist nicht schön.
Cameron Lowell Palmer
8

Ich fand das die beste Lösung für mich. Sie müssen etwas Kreativität einsetzen, um Ihre eigenen Anpassungen vorzunehmen

In Ihrer MKAnnotationViewUnterklasse können Sie verwenden

- (void)didAddSubview:(UIView *)subview{
    int image = 0;
    int labelcount = 0;
    if ([[[subview class] description] isEqualToString:@"UICalloutView"]) {
        for (UIView *subsubView in subview.subviews) {
            if ([subsubView class] == [UIImageView class]) {
                UIImageView *imageView = ((UIImageView *)subsubView);
                switch (image) {
                    case 0:
                        [imageView setImage:[UIImage imageNamed:@"map_left"]];
                        break;
                    case 1:
                        [imageView setImage:[UIImage imageNamed:@"map_right"]];
                        break;
                    case 3:
                        [imageView setImage:[UIImage imageNamed:@"map_arrow"]];
                        break;
                    default:
                        [imageView setImage:[UIImage imageNamed:@"map_mid"]];
                        break;
                }
                image++;
            }else if ([subsubView class] == [UILabel class]) {
                UILabel *labelView = ((UILabel *)subsubView);
                switch (labelcount) {
                    case 0:
                        labelView.textColor = [UIColor blackColor];
                        break;
                    case 1:
                        labelView.textColor = [UIColor lightGrayColor];
                        break;

                    default:
                        break;
                }
                labelView.shadowOffset = CGSizeMake(0, 0);
                [labelView sizeToFit];
                labelcount++;
            }
        }
    }
}

Und wenn das ein subviewist UICalloutView, dann kannst du damit herumdrehen und was drin ist.

яοвοτағτєяа ււ
quelle
1
Wie können Sie überprüfen, ob die Unteransicht eine UICalloutView ist, da UICalloutview keine öffentliche Klasse ist?
Manish Ahuja
if ([[[subview class] description] isEqualToString:@"UICalloutView"]) Ich denke, es gibt einen besseren Weg, aber das funktioniert.
яοвοτағτєяа 19
Hattest du irgendwelche Probleme damit, da es, wie Manish betonte, eine Privatklasse ist?
Daniel
Wahrscheinlich haben sie die UIView-Struktur für die Calloutviews geändert. Ich habe die App, die dies verwendet, nicht aktualisiert, also bist du allein: P
яοвοτағτєяа 21
6

Ich hatte das gleiche Problem. Es gibt eine Reihe von Blog-Posts zu diesem Thema in diesem Blog http://spitzkoff.com/craig/?p=81 .

Nur die Verwendung von MKMapViewDelegatehilft Ihnen hier nicht weiter und das Unterklassen MKMapViewund der Versuch, die vorhandene Funktionalität zu erweitern, hat auch bei mir nicht funktioniert.

CustomCalloutViewAm Ende habe ich mein eigenes geschaffen , das ich über meinem habe MKMapView. Sie können diese Ansicht beliebig gestalten.

My CustomCalloutViewhat eine ähnliche Methode wie diese:


- (void) openForAnnotation: (id)anAnnotation
{
    self.annotation = anAnnotation;
    // remove from view
    [self removeFromSuperview];

    titleLabel.text = self.annotation.title;

    [self updateSubviews];
    [self updateSpeechBubble];

    [self.mapView addSubview: self];
}

Es nimmt ein MKAnnotationObjekt und setzt seinen eigenen Titel. Anschließend ruft es zwei andere hässliche Methoden auf, die die Breite und Größe des Callout-Inhalts anpassen und anschließend die Sprechblase an der richtigen Position um das Objekt ziehen.

Schließlich wird die Ansicht als Unteransicht zur mapView hinzugefügt. Das Problem bei dieser Lösung besteht darin, dass es schwierig ist, das Callout an der richtigen Position zu halten, wenn die Kartenansicht gescrollt wird. Ich verstecke nur das Callout in der Delegate-Methode für Kartenansichten für eine Regionsänderung, um dieses Problem zu lösen.

Es hat einige Zeit gedauert, um all diese Probleme zu lösen, aber jetzt verhält sich das Callout fast wie das offizielle, aber ich habe es in meinem eigenen Stil.

Sascha Konietzke
quelle
Ich stimme dem zu: UICalloutView ist eine private Klasse und im SDK offiziell nicht verfügbar. Am besten folgen Sie Saschas Rat.
JoePasq
Hallo Sascha, wir danken dir für deine Antwort. Das aktuelle Problem besteht jedoch darin, die Position (x, y und nicht lat oder long) auf der Karte zu ermitteln, an der die Pins angezeigt werden, damit wir die Callout-Ansicht anzeigen können.
Zach
1
Das Konvertieren von Koordinaten kann einfach mit zwei Funktionen von MKMapView durchgeführt werden: # - convertCoordinate: toPointToView: # - convertPoint: toCoordinateFromView:
Sascha Konietzke
5

Um dies zu lösen, muss man grundsätzlich: a) verhindern, dass die Standard-Callout-Blase auftaucht. b) Finden Sie heraus, auf welche Anmerkung geklickt wurde.

Ich konnte dies erreichen, indem ich: a) canShowCallout auf NO setzte b) Unterklassen, MKPinAnnotationView und die Methoden touchBegan und touchEnd überschrieb.

Hinweis: Sie müssen die Berührungsereignisse für MKAnnotationView und nicht für MKMapView verarbeiten

Shivaraj Tenginakai
quelle
3

Ich habe mir gerade einen Ansatz ausgedacht, die Idee hier ist

  // Detect the touch point of the AnnotationView ( i mean the red or green pin )
  // Based on that draw a UIView and add it to subview.
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
{
    CGPoint newPoint = [self.mapView convertCoordinate:selectedCoordinate toPointToView:self.view];
//    NSLog(@"regionWillChangeAnimated newPoint %f,%f",newPoint.x,newPoint.y);
    [testview  setCenter:CGPointMake(newPoint.x+5,newPoint.y-((testview.frame.size.height/2)+35))];
    [testview setHidden:YES];
}

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
    CGPoint newPoint = [self.mapView convertCoordinate:selectedCoordinate toPointToView:self.view];
//    NSLog(@"regionDidChangeAnimated newPoint %f,%f",newPoint.x,newPoint.y);
    [testview  setCenter:CGPointMake(newPoint.x,newPoint.y-((testview.frame.size.height/2)+35))];
    [testview setHidden:NO];
}

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view 
{  
    NSLog(@"Select");
    showCallout = YES;
    CGPoint point = [self.mapView convertPoint:view.frame.origin fromView:view.superview];
    [testview setHidden:NO];
    [testview  setCenter:CGPointMake(point.x+5,point.y-(testview.frame.size.height/2))];
    selectedCoordinate = view.annotation.coordinate;
    [self animateIn];
}

- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view 
{
    NSLog(@"deSelect");
    if(!showCallout)
    {
        [testview setHidden:YES];
    }
}

Hier - Testansicht ist eine UIViewGröße von 320x100 - ShowCallout ist BOOL - [self animateIn];ist die Funktion, mit der Animationen angezeigt werden UIAlertView.

Nikesh K.
quelle
Was gibt selectedCoordinate an? Was für ein Objekt ist das?
Pradeep Reddy Kypa
Es ist ein CLLocationCoordinate2d-Objekt.
Bharathi Kumar
1

Sie können leftCalloutView verwenden und annotation.text auf @ "" setzen.

Nachfolgend finden Sie den Beispielcode:

pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
if(pinView == nil){
    pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:defaultPinID] autorelease];       
}
CGSize sizeText = [annotation.title sizeWithFont:[UIFont fontWithName:@"HelveticaNeue" size:12] constrainedToSize:CGSizeMake(150, CGRectGetHeight(pinView.frame))                                 lineBreakMode:UILineBreakModeTailTruncation];
pinView.canShowCallout = YES;    
UILabel *lblTitolo = [[UILabel alloc] initWithFrame:CGRectMake(2,2,150,sizeText.height)];
lblTitolo.text = [NSString stringWithString:ann.title];
lblTitolo.font = [UIFont fontWithName:@"HelveticaNeue" size:12];
lblTitolo.lineBreakMode = UILineBreakModeTailTruncation;
lblTitolo.numberOfLines = 0;
pinView.leftCalloutAccessoryView = lblTitolo;
[lblTitolo release];
annotation.title = @" ";            
Fabio Napodano
quelle
1
Dies wird bis zu einem gewissen Grad "funktionieren", beantwortet aber die allgemeinere Frage nicht wirklich und ist sehr hackisch.
Cameron Lowell Palmer
1

Ich habe meine Gabelung des hervorragenden SMCalloutView herausgeschoben, das das Problem löst, indem eine benutzerdefinierte Ansicht für Beschriftungen bereitgestellt wird und flexible Breiten / Höhen ziemlich schmerzlos möglich sind. Es gibt noch einige Macken zu lösen, aber es ist bisher ziemlich funktional:

https://github.com/u10int/calloutview

u10int
quelle