Come personalizzare la bolla di callout per MKAnnotationView?


88

Attualmente sto lavorando con il mapkit e sono bloccato.

Sto utilizzando una vista annotazione personalizzata e desidero utilizzare la proprietà dell'immagine per visualizzare il punto sulla mappa con la mia icona. Questo funziona bene. Ma quello che vorrei fare è anche sovrascrivere la visualizzazione callout predefinita (la bolla che appare con il titolo / sottotitolo quando viene toccata l'icona dell'annotazione). Voglio essere in grado di controllare il callout stesso: il mapkit fornisce solo l'accesso alle viste di callout ausiliarie sinistra e destra, ma non è possibile fornire una visualizzazione personalizzata per la bolla di callout, o per dargli dimensione zero o qualsiasi altra cosa.

La mia idea era di sovrascrivere selectAnnotation / deselectAnnotation in my MKMapViewDelegate, quindi disegnare la mia vista personalizzata effettuando una chiamata alla mia vista di annotazione personalizzata. Funziona, ma solo quando canShowCalloutè impostato su YESnella mia classe di visualizzazione delle annotazioni personalizzate. Questi metodi NON vengono chiamati se ho impostato su NO(che è quello che voglio, in modo che la bolla di callout predefinita non venga disegnata). Quindi non ho modo di sapere se l'utente ha toccato il mio punto sulla mappa (selezionato) o ha toccato un punto che non fa parte delle mie viste di annotazione (eliminato) senza che venga visualizzata la vista a bolle di callout predefinita.

Ho provato a seguire un percorso diverso e gestire da solo tutti gli eventi di tocco nella mappa, e non riesco a farlo funzionare. Ho letto altri post relativi alla cattura di eventi tattili nella visualizzazione mappa, ma non sono esattamente ciò che voglio. C'è un modo per scavare nella vista mappa per rimuovere la bolla di richiamo prima di disegnare? Sono in perdita.

Eventuali suggerimenti? Mi manca qualcosa di ovvio?


Questo collegamento non funziona, ma ho trovato lo stesso post qui -> Creazione di callout di annotazioni su mappa personalizzata - Parte 1
Fede Mika

2
Puoi fare riferimento a questo progetto per una rapida demo. github.com/akshay1188/CustomAnnotation Compilazione delle risposte sopra in una demo in esecuzione.
akshay1188

Risposte:


54

C'è una soluzione ancora più semplice.

Crea una personalizzazione UIView(per il tuo callout).

Quindi crea una sottoclasse di MKAnnotationViewe sovrascrivi setSelectedcome segue:

- (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, lavoro fatto.


8
ciao TappCandy! Innanzitutto, grazie per la tua soluzione. Funziona, ma quando carico una vista (lo sto facendo da un file pennino) i pulsanti non funzionano. Cosa posso fare per farli funzionare correttamente ?? Grazie
Frade

Adoro la semplicità di questa soluzione!
Eric Brotto

6
Hmm - questo non funziona! Sostituisce l'annotazione della mappa (cioè il segnaposto) NON il callout "bolla".
PapillonUK

2
Questo non funzionerà. MKAnnotationView non ha un riferimento alla mapview, quindi hai diversi problemi con l'aggiunta di un callout personalizzato. Ad esempio, non sai se aggiungerlo come sottoview sarà fuori schermo.
Cameron Lowell Palmer

@PapillonUK Hai il controllo sul frame del callout. Non sta sostituendo il perno, appare sopra di esso. Regola la sua posizione quando lo aggiungi come sottoview.
Ben Packard

40

detailCalloutAccessoryView

Ai vecchi tempi questo era un problema, ma Apple lo ha risolto, basta controllare i documenti su MKAnnotationView

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

Davvero, questo è tutto. Accetta qualsiasi UIView.


Qual è il migliore da usare?
Satyam

13

Continuando dalla risposta brillantemente semplice di @ TappCandy, se vuoi animare la tua bolla allo stesso modo di quella predefinita, ho prodotto questo metodo di animazione:

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

Sembra abbastanza complicato, ma fintanto che il punto della bolla del callout è progettato per essere al centro in basso, dovresti essere in grado di sostituirlo myBubbleWidthe myBubbleHeightcon la tua dimensione affinché funzioni. E ricorda di assicurarti che le tue visualizzazioni secondarie abbiano la loro autoResizeMaskproprietà impostata su 63 (cioè "all") in modo che vengano ridimensionate correttamente nell'animazione.

: -Joe


A proposito, puoi animare sulla proprietà scale piuttosto che sul frame, al fine di ottenere un ridimensionamento corretto del contenuto.
occulus

Non utilizzare CGRectMake. Usa CGTransform.
Cameron Lowell Palmer

@occulus - Penso di averlo provato prima, ma ho avuto problemi di rendering in iOS 4. Ma potrebbe essere stato perché non stavo usando CABasicAnimation... Troppo tempo fa per ricordarlo! So che dovrei animare anche la proprietà y, a causa dell'offset centrale.
jowie

@CameronLowellPalmer - perché non utilizzare CGRectMake?
jowie

@jowie Perché la sintassi è migliore ed evita i valori magici nel codice. CGAffineTransformMakeScale (1.1f, 1.1f); Far crescere la scatola del 110% è chiaro. Il modo in cui l'hai fatto potrebbe funzionare, ma non è carino.
Cameron Lowell Palmer

8

Ho trovato questa la soluzione migliore per me. Dovrai usare un po 'di creatività per fare le tue personalizzazioni

Nella tua MKAnnotationViewsottoclasse puoi usare

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

E se subviewè un UICalloutView, allora puoi rovinarlo e cosa c'è dentro.


1
come si controlla se la sottoview è una UICalloutView poiché UICalloutview non è una classe pubblica.
Manish Ahuja

if ([[[subview class] description] isEqualToString:@"UICalloutView"]) Penso che ci sia un modo migliore, ma funziona.
яοвοτағτєяа ււ

hai avuto problemi con questo dato che, come ha sottolineato Manish, è una lezione privata.
Daniel

Probabilmente hanno cambiato la struttura UIView per le visualizzazioni dei callout. Non ho aggiornato l'app che lo utilizza, quindi sei da solo: P
яοвοτағτєяа ււ

6

Ho avuto lo stesso problema. C'è una serie di post su questo argomento su questo blog http://spitzkoff.com/craig/?p=81 .

Il solo utilizzo di MKMapViewDelegatenon ti aiuta qui e anche la creazione di sottoclassi MKMapViewe il tentativo di estendere la funzionalità esistente non ha funzionato per me.

Quello che ho finito per fare è creare il mio CustomCalloutViewche sto avendo sopra il mio MKMapView. Puoi modellare questa vista nel modo che preferisci.

Il mio CustomCalloutViewha un metodo simile a questo:


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

Prende un MKAnnotationoggetto e imposta il proprio titolo, quindi chiama altri due metodi piuttosto brutti che regolano la larghezza e la dimensione del contenuto del callout e successivamente disegnano il fumetto attorno ad esso nella posizione corretta.

Infine la vista viene aggiunta come sottoview al mapView. Il problema con questa soluzione è che è difficile mantenere il callout nella posizione corretta quando si scorre la visualizzazione della mappa. Sto solo nascondendo il callout nel metodo delegato delle viste mappa su un cambio di regione per risolvere questo problema.

Ci è voluto del tempo per risolvere tutti quei problemi, ma ora il callout si comporta quasi come quello ufficiale, ma ce l'ho nel mio stile.


Secondo quello: UICalloutView è una classe privata e non è disponibile ufficialmente nell'SDK. La soluzione migliore è seguire il consiglio di Sascha.
JoePasq

Ciao Sascha, ti ringraziamo per la tua risposta. Ma il problema attuale che stiamo riscontrando è come ottenere la posizione (x, y e non lat o long) sulla mappa in cui compaiono i pin, in modo da poter visualizzare la vista callout.
Zach,

1
la conversione delle coordinate può essere facilmente eseguita da due funzioni di MKMapView: # - convertCoordinate: toPointToView: # - convertPoint: toCoordinateFromView:
Sascha Konietzke

5

Fondamentalmente per risolvere questo problema, è necessario: a) Impedire la comparsa della bolla di richiamo predefinita. b) Determina quale annotazione è stata cliccata.

Sono stato in grado di ottenere questi risultati: a) impostando canShowCallout su NO b) sottoclasse, MKPinAnnotationView e sovrascrivendo i metodi touchesBegan e touchesEnd.

Nota: è necessario gestire gli eventi di tocco per MKAnnotationView e non per MKMapView


3

Ho appena escogitato un approccio, l'idea qui è

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

Qui - testview è UIViewdi dimensioni 320x100 - showCallout è BOOL - [self animateIn];è la funzione che fa visualizzare l'animazione come UIAlertView.


Cosa specifica selectedCoordinate? Che tipo di oggetto è quello?
Pradeep Reddy Kypa

è l'oggetto CLLocationCoordinate2d.
bharathi kumar

1

Puoi utilizzare leftCalloutView, impostando annotation.text su @ ""

Di seguito trovi il codice di esempio:

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
Questo "funzionerà" in una certa misura, ma in realtà non risponde alla domanda più generale posta ed è molto hacker.
Cameron Lowell Palmer

1

Ho spinto fuori il mio fork dell'eccellente SMCalloutView che risolve il problema fornendo una visualizzazione personalizzata per i callout e consentendo larghezze / altezze flessibili in modo abbastanza indolore. Ancora alcune stranezze da risolvere, ma finora è abbastanza funzionale:

https://github.com/u10int/calloutview

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.