Comment personnaliser la bulle de légende pour MKAnnotationView?

88

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

J'utilise une vue d'annotation personnalisée et je souhaite utiliser la propriété image pour afficher le point sur la carte avec ma propre icône. J'ai cela fonctionne bien. Mais ce que j'aimerais aussi faire est de 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 / déselectAnnotation dans my MKMapViewDelegate, puis de dessiner ma propre vue personnalisée en appelant ma vue d'annotation personnalisée. Cela fonctionne, mais uniquement lorsque canShowCalloutest défini sur YESdans ma classe de vue d'annotation personnalisée. Ces méthodes ne sont PAS appelées si ce paramètre est défini 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 de bulle de légende par défaut apparaisse.

J'ai essayé de suivre un chemin différent et de gérer moi-même tous les événements tactiles dans 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 la carte pour supprimer la bulle de légende avant de dessiner? Je suis à perte.

Aucune suggestion? Est-ce que je rate quelque chose d'évident?

Zach
la source
Ce lien ne fonctionne pas, mais a trouvé le même message ici -> Créer des légendes d'annotations de carte personnalisées - Partie 1
Fede Mika
2
Vous pouvez vous référer à ce projet pour une démonstration rapide. github.com/akshay1188/CustomAnnotation Compilation des réponses ci-dessus en une seule démo en cours d'exécution.
akshay1188

Réponses:

53

Il existe une solution encore plus simple.

Créez une personnalisation UIView(pour votre légende).

Créez ensuite une sous-classe de MKAnnotationViewet remplacez setSelectedcomme 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, travail fait.

TappCandy
la source
8
salut TappCandy! Tout d'abord, merci pour votre solution. Cela fonctionne, mais lorsque vous chargez une vue (je le fais à partir d'un fichier nib), les boutons ne fonctionnent pas. Que puis-je faire pour les faire fonctionner correctement? Merci
Frade
J'adore la simplicité de cette solution!
Eric Brotto
6
Hmm - cela ne fonctionne pas! Il remplace l'annotation de la carte (c'est-à-dire la broche) PAS la légende "bulle".
PapillonUK
2
Cela ne fonctionnera pas. MKAnnotationView n'a pas de référence à la vue cartographique, vous rencontrez donc plusieurs problèmes avec l'ajout d'une légende personnalisée. Par exemple, vous ne savez pas si l'ajout de ceci en tant que sous-vue sera hors écran.
Cameron Lowell Palmer
@PapillonUK Vous contrôlez le cadre de la légende. Il ne remplace pas la broche, elle apparaît par-dessus. Ajustez sa position lorsque vous l'ajoutez en tant que sous-vue.
Ben Packard
40

detailCalloutAccessoryView

Autrefois, c'était pénible , mais Apple l'a résolu, il suffit de consulter la documentation sur MKAnnotationView

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

Vraiment, c'est ça. Prend n'importe quel UIView.

Cameron Lowell Palmer
la source
Lequel est le meilleur à utiliser?
Satyam
13

Dans la continuité de la réponse brillamment simple de @ TappCandy, si vous souhaitez animer votre bulle de la même manière que celle 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 de légende est conçu pour être au centre, vous devriez simplement pouvoir le remplacer myBubbleWidthet myBubbleHeightavec votre propre taille pour que cela fonctionne. Et n'oubliez pas de vous assurer que vos sous-vues ont leur autoResizeMaskpropriété définie sur 63 (c'est-à-dire "toutes") afin qu'elles soient correctement mises à l'échelle dans l'animation.

: -Joe

jowie
la source
Btw, vous pouvez animer sur la propriété d'échelle plutôt que sur le cadre, afin d'obtenir une mise à l'échelle appropriée du contenu.
occulus
N'utilisez pas CGRectMake. Utilisez CGTransform.
Cameron Lowell Palmer
@occulus - Je pense que j'ai essayé cela en premier, mais j'ai eu des problèmes de rendu sous iOS 4. Mais c'est peut-être parce que je n'utilisais pas CABasicAnimation... Il y a trop longtemps pour m'en souvenir! Je sais que je devrais également animer la propriété y, en raison du décalage central.
jowie
@CameronLowellPalmer - pourquoi ne pas l'utiliser CGRectMake?
jowie
@jowie Parce que la syntaxe est meilleure et évite les valeurs magiques dans votre code. CGAffineTransformMakeScale (1.1f, 1.1f); Faire grandir la boîte de 110% est clair. La façon dont vous avez procédé peut fonctionner, mais ce n'est pas joli.
Cameron Lowell Palmer
8

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 MKAnnotationViewsous-classe, 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 subviewest un UICalloutView, alors vous pouvez visser avec, et ce qu'il y a à l'intérieur.

яοвοτағτєяа ււ
la source
1
comment vérifier si la sous-vue est une UICalloutView puisque UICalloutview n'est pas une classe publique.
Manish Ahuja
if ([[[subview class] description] isEqualToString:@"UICalloutView"]) Je pense qu'il existe un meilleur moyen, mais cela fonctionne.
яοвοτағτєяа ււ
avez-vous eu des problèmes avec cela puisque, comme Manish l'a souligné, c'est un cours privé.
Daniel
Ils ont probablement changé la structure UIView pour les vues de légende. Je n'ai pas mis à jour l'application qui l'utilise, vous êtes donc seul: P
яοвοτағτєяа ււ
6

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

Le simple fait d'utiliser le MKMapViewDelegatene vous aide pas ici et le sous-classement MKMapViewet essayer d'étendre la fonctionnalité existante ne fonctionnait pas non plus pour moi.

Ce que j'ai fini par faire, c'est créer le mien CustomCalloutViewque j'ai en plus du mien MKMapView. Vous pouvez personnaliser cette vue comme vous le souhaitez.

My CustomCalloutViewa 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 MKAnnotationobjet et définit son propre titre, puis il appelle deux autres méthodes assez laides qui ajustent la largeur et la taille du contenu de la légende, puis dessinent la bulle autour de lui à la bonne position.

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 bonne position lorsque la vue de la carte défile. Je cache simplement la légende dans la méthode de délégué des vues de carte sur un changement de région pour résoudre ce problème.

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

Sascha Konietzke
la source
Je confirme que: UICalloutView est une classe privée et n'est pas disponible officiellement dans le SDK. Votre meilleur pari est de suivre les conseils de Sascha.
JoePasq
Salut Sascha, Nous vous remercions pour votre réponse. Mais le problème actuel que nous rencontrons est de savoir comment obtenir la position (x, y et non lat ou long) sur la carte où les broches apparaissent, afin que nous puissions afficher la vue de légende.
Zach
1
la conversion des coordonnées peut facilement être effectuée par deux fonctions de MKMapView: # - convertCoordinate: toPointToView: # - convertPoint: toCoordinateFromView:
Sascha Konietzke
5

Fondamentalement, pour résoudre ce problème, il faut: a) Empêcher l'apparition de la bulle de légende par défaut. b) Identifiez l'annotation sur laquelle vous avez cliqué.

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

Shivaraj Tenginakai
la source
3

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 UIViewde taille 320x100 - showCallout est BOOL - [self animateIn];est la fonction qui permet de visualiser l'animation comme UIAlertView.

Nikesh K
la source
Que spécifie selectedCoordinate? De quel type d'objet s'agit-il?
Pradeep Reddy Kypa
c'est l'objet CLLocationCoordinate2d.
bharathi kumar
1

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 = @" ";            
Fabio Napodano
la source
1
Cela `` fonctionnera '' dans une certaine mesure, mais ne répond pas vraiment à la question plus générale posée et c'est très hackish.
Cameron Lowell Palmer
1

J'ai sorti 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ésoudre, mais c'est assez fonctionnel jusqu'à présent:

https://github.com/u10int/calloutview

u10int
la source