web-dev-qa-db-fra.com

Comment personnaliser la bulle de légende pour MKAnnotationView?

Je travaille actuellement avec le mapkit et je suis bloqué.

J'ai une vue d'annotation personnalisée que j'utilise et je souhaite utiliser la propriété image pour afficher le point sur la carte avec ma propre icône. J'ai ce bon fonctionnement. Mais ce que j'aimerais aussi faire, c'est remplacer la vue de légende par défaut (la bulle qui apparaît avec le titre/sous-titre lorsque l'icône d'annotation est touchée). Je veux pouvoir contrôler la légende elle-même: le mapkit ne donne accès qu'aux vues de légende auxiliaires gauche et droite, mais aucun moyen de fournir une vue personnalisée pour la bulle de légende, ou de lui donner une taille nulle, ou quoi que ce soit d'autre.

Mon idée était de remplacer selectAnnotation/deselectAnnotation dans mon MKMapViewDelegate, puis de dessiner ma propre vue personnalisée en appelant ma vue d'annotation personnalisée. Cela fonctionne, mais uniquement lorsque canShowCallout est défini sur YES dans ma classe de vue d'annotation personnalisée. Ces méthodes ne sont PAS appelées si j'ai cet ensemble sur NO (ce que je veux, pour que la bulle de légende par défaut ne soit pas dessinée). Je n'ai donc aucun moyen de savoir si l'utilisateur a touché mon point sur la carte (l'a sélectionné) ou a touché un point qui ne fait pas partie de mes vues d'annotation (l'a supprimé) sans que la vue bulle par défaut n'apparaisse.

J'ai essayé de suivre un chemin différent et de gérer moi-même tous les événements tactiles sur la carte, et je n'arrive pas à faire fonctionner cela. J'ai lu d'autres articles liés à la capture d'événements tactiles dans la vue de la carte, mais ils ne sont pas exactement ce que je veux. Existe-t-il un moyen de creuser dans la vue de carte pour supprimer la bulle de légende avant de dessiner? Je suis à perte.

Aucune suggestion? Suis-je en train de manquer quelque chose d'évident?

88
Zach

Il existe une solution encore plus simple.

Créez un UIView personnalisé (pour votre légende).

Créez ensuite une sous-classe de MKAnnotationView et remplacez setSelected comme suit:

- (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, le travail est fait.

53
TappCandy

detailCalloutAccessoryView

Dans les temps anciens, c'était pénible, mais Apple l'a résolu, il suffit de vérifier les documents sur MKAnnotationView

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

Vraiment, c'est tout. Prend tout UIView.

40

Dans la continuité de la réponse brillamment simple de @ TappCandy, si vous souhaitez animer votre bulle de la même manière que la bulle par défaut, j'ai produit cette méthode d'animation:

- (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);
            }];
        }];
    }];
}

Cela semble assez compliqué, mais tant que le point de votre bulle d'accroche est conçu pour être au centre-bas, vous devriez simplement être en mesure de remplacer myBubbleWidth et myBubbleHeight par votre propre taille. travail. Et n'oubliez pas de vous assurer que vos sous-vues ont leur propriété autoResizeMask définie sur 63 (c'est-à-dire "tous") afin qu'elles évoluent correctement dans l'animation.

: -Joe

13
jowie

J'ai trouvé que c'était la meilleure solution pour moi. Vous devrez faire preuve de créativité pour faire vos propres personnalisations

Dans votre sous-classe MKAnnotationView, vous pouvez utiliser

- (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++;
            }
        }
    }
}

Et si le subview est un UICalloutView, alors vous pouvez le visser et ce qu'il contient.

J'ai eu le même problème. Il y a de nombreux articles de blog sur ce sujet sur ce blog http://spitzkoff.com/craig/?p=81 .

Utiliser simplement le MKMapViewDelegate ne vous aide pas ici et sous-classer MKMapView et essayer d'étendre les fonctionnalités existantes n'a pas fonctionné pour moi.

Ce que j'ai fini par faire, c'est de créer mon propre CustomCalloutView que j'ai sur mon MKMapView. Vous pouvez styliser cette vue comme vous le souhaitez.

Mon CustomCalloutView a une méthode similaire à celle-ci:


- (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];
}

Il prend un objet MKAnnotation et définit son propre titre, ensuite il appelle deux autres méthodes assez laides qui ajustent la largeur et la taille du contenu de la légende et dessinent ensuite la bulle autour d'elle à la position correcte.

Enfin, la vue est ajoutée en tant que sous-vue à la mapView. Le problème avec cette solution est qu'il est difficile de conserver la légende à la position correcte lorsque la vue de la carte défile. Je cache simplement la légende dans la méthode déléguée des vues de carte sur un changement de région pour résoudre ce problème.

Il a fallu un certain temps pour résoudre tous ces problèmes, mais maintenant la légende se comporte presque comme celle officielle, mais je l'ai dans mon propre style.

6
Sascha Konietzke

Fondamentalement, pour résoudre ce problème, il faut: a) Empêcher la bulle de légende par défaut d'apparaître. b) Déterminez quelle annotation a été cliquée.

J'ai pu y parvenir en: a) définissant canShowCallout sur NO b) en sous-classant, MKPinAnnotationView et en remplaçant les méthodes touchesBegan et touchesEnd.

Remarque: Vous devez gérer les événements tactiles pour MKAnnotationView et non MKMapView

5

Je viens de proposer une approche, l'idée ici est

  // 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];
    }
}

Ici - testview est un UIView de taille 320x100 - showCallout est BOOL - [self animateIn]; est la fonction qui affiche l'animation comme UIAlertView.

3
Nikesh K

Vous pouvez utiliser leftCalloutView, en définissant annotation.text sur @ ""

Veuillez trouver ci-dessous l'exemple de code:

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 = @" ";            
1
Fabio Napodano

J'ai poussé ma fourchette de l'excellent SMCalloutView qui résout le problème en fournissant une vue personnalisée pour les légendes et en permettant des largeurs/hauteurs flexibles assez facilement. Encore quelques bizarreries à régler, mais c'est assez fonctionnel jusqu'à présent:

https://github.com/u10int/calloutview

1
u10int