Como personalizar o balão de chamada para MKAnnotationView?

88

Atualmente estou trabalhando com o mapkit e estou preso.

Estou usando uma visualização de anotação personalizada e desejo usar a propriedade image para exibir o ponto no mapa com meu próprio ícone. Eu tenho isso funcionando bem. Mas o que eu também gostaria de fazer é substituir a visualização de texto explicativo padrão (a bolha que aparece com o título / subtítulo quando o ícone de anotação é tocado). Eu quero ser capaz de controlar o texto explicativo em si: o mapkit fornece acesso apenas às visualizações de texto explicativo auxiliares esquerda e direita, mas nenhuma maneira de fornecer uma visualização personalizada para o balão de texto explicativo, ou dar a ele tamanho zero, ou qualquer outra coisa.

Minha ideia era substituir selectAnnotation / deselectAnnotation em my MKMapViewDelegatee, em seguida, desenhar minha própria visualização personalizada fazendo uma chamada para minha visualização de anotação personalizada. Isso funciona, mas apenas quando canShowCalloutestá definido como YESna minha classe de visualização de anotação personalizada. Esses métodos NÃO são chamados se eu tiver definido como NO(que é o que eu quero, para que o balão de texto explicativo padrão não seja desenhado). Portanto, não tenho como saber se o usuário tocou em meu ponto no mapa (o selecionou) ou tocou em um ponto que não faz parte de minhas visualizações de anotação (o excluiu) sem que a visualização de bolha de texto explicativo padrão apareça.

Tentei seguir um caminho diferente e apenas lidar com todos os eventos de toque no mapa, e não consigo fazer isso funcionar. Eu li outras postagens relacionadas à captura de eventos de toque na visualização do mapa, mas não são exatamente o que eu quero. Existe uma maneira de explorar a visualização do mapa para remover o balão de texto explicativo antes de desenhar? Estou perdida.

Alguma sugestão? Estou perdendo algo óbvio?

Zach
fonte
Este link não funciona, mas encontrou a mesma postagem aqui -> Criando textos explicativos de anotação de mapa personalizado - Parte 1
Fede Mika
2
Você pode consultar este projeto para uma rápida demonstração. github.com/akshay1188/CustomAnnotation Compilação das respostas acima em uma demonstração em execução.
akshay1188

Respostas:

53

Existe uma solução ainda mais fácil.

Crie um personalizado UIView(para sua frase de destaque).

Em seguida, crie uma subclasse de MKAnnotationViewe substitua da setSelectedseguinte maneira:

- (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, trabalho feito.

TappCandy
fonte
8
oi TappCandy! Em primeiro lugar, obrigado pela sua solução. Funciona, mas quando carrego uma visualização (estou fazendo isso a partir de um arquivo nib), os botões não funcionam. O que posso fazer para que funcionem bem ?? Obrigado
Frade
Ame a simplicidade desta solução!
Eric Brotto,
6
Hmm - isso não funciona! Ele substitui a anotação do mapa (ou seja, o pino) NÃO a chamada "bolha".
PapillonUK
2
Isso não vai funcionar. O MKAnnotationView não tem uma referência ao mapview, portanto, você tem vários problemas ao adicionar uma chamada personalizada. Por exemplo, você não sabe se adicionar isso como uma subvisão ficará fora da tela.
Cameron Lowell Palmer
@PapillonUK Você tem controle sobre o quadro do texto explicativo. Não está substituindo o pino, está aparecendo em cima dele. Ajuste sua posição ao adicioná-la como uma subvisualização.
Ben Packard de
40

detailCalloutAccessoryView

Antigamente isso era uma dor, mas a Apple resolveu, basta verificar a documentação em MKAnnotationView

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

Realmente, é isso. Tira qualquer UIView.

Cameron Lowell Palmer
fonte
Qual é o melhor para usar?
Satyam de
13

Continuando com a resposta brilhantemente simples de @TappCandy, se você quiser animar sua bolha da mesma forma que a bolha padrão, produzi este método de animação:

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

Parece bastante complicado, mas contanto que a ponta de sua bolha de texto explicativo seja projetada para ficar na parte central inferior, você deve apenas ser capaz de substituir myBubbleWidthe myBubbleHeightcom seu próprio tamanho para que funcione. E lembre-se de certificar-se de que suas subvisualizações tenham suas autoResizeMaskpropriedades definidas como 63 (ou seja, "todas") para que sejam dimensionadas corretamente na animação.

: -Joe

jowie
fonte
A propósito, você poderia animar na propriedade de escala ao invés do quadro, a fim de obter o dimensionamento adequado do conteúdo.
outubro
Não use CGRectMake. Use CGTransform.
Cameron Lowell Palmer
@occulus - Acho que tentei primeiro, mas tive problemas de renderização no iOS 4. Mas isso pode ter sido porque eu não estava usando CABasicAnimation... Há muito tempo para lembrar! Eu sei que teria que animar a propriedade y também, devido ao deslocamento central.
Jowie
@CameronLowellPalmer - por que não usar CGRectMake?
Jowie
@jowie Porque a sintaxe é melhor e evita os valores mágicos em seu código. CGAffineTransformMakeScale (1.1f, 1.1f); Aumentar a caixa em 110% é claro. A maneira como você fez isso pode funcionar, mas não é bonita.
Cameron Lowell Palmer
8

Esta é a melhor solução para mim. Você terá que usar um pouco de criatividade para fazer suas próprias personalizações

Em sua MKAnnotationViewsubclasse, você pode usar

- (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 subviewfor um UICalloutView, então você pode mexer com ele e o que está dentro dele.

яοвοτағτєяа ււ
fonte
1
como você verifica se a subvisualização é uma UICalloutView, uma vez que UICalloutview não é uma classe pública.
Manish Ahuja
if ([[[subview class] description] isEqualToString:@"UICalloutView"]) Acho que tem um jeito melhor, mas funciona.
яοвοτағτєяа ււ
você teve algum problema com isso desde que, como Manish apontou, é uma aula particular.
Daniel
Provavelmente, eles mudaram a estrutura UIView para as visualizações de chamada. Não atualizei o aplicativo que usa isso, então você está por conta própria: P
яοвοτағτєяа ււ
6

Eu tive o mesmo problema. Há uma série de postagens de blog sobre esse tópico neste blog http://spitzkoff.com/craig/?p=81 .

Apenas usar o MKMapViewDelegatenão ajuda você aqui e criar subclasses MKMapViewe tentar estender a funcionalidade existente também não funcionou para mim.

O que acabei fazendo é criar o meu próprio CustomCalloutViewque estou tendo em cima do meu MKMapView. Você pode definir o estilo dessa visualização da maneira que desejar.

My CustomCalloutViewtem um método semelhante a este:


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

Ele pega um MKAnnotationobjeto e define seu próprio título, depois chama dois outros métodos bastante feios que ajustam a largura e o tamanho do conteúdo do texto explicativo e, em seguida, desenha o balão de fala em torno dele na posição correta.

Por fim, a visualização é adicionada como uma subvisualização ao mapView. O problema com esta solução é que é difícil manter o texto explicativo na posição correta quando a visualização do mapa é rolada. Estou apenas ocultando o texto explicativo no método de delegação de visualizações de mapa em uma alteração de região para resolver este problema.

Demorou um pouco para resolver todos aqueles problemas, mas agora o texto explicativo quase se comporta como o oficial, mas tenho meu próprio estilo.

Sascha Konietzke
fonte
Eu em segundo lugar: UICalloutView é uma classe privada e não está oficialmente disponível no SDK. Sua melhor aposta é seguir o conselho de Sascha.
JoePasq,
Olá Sascha, Agradecemos sua resposta. Mas, o problema atual que estamos enfrentando é como obter a posição (x, y e não lat ou long) no mapa onde os pinos aparecem, para que possamos exibir a visualização do texto explicativo.
Zach
1
a conversão de coordenadas pode ser feita facilmente por duas funções de MKMapView: # - convertCoordinate: toPointToView: # - convertPoint: toCoordinateFromView:
Sascha Konietzke
5

Basicamente, para resolver isso, é necessário: a) Evitar que o balão de texto explicativo padrão apareça. b) Descubra qual anotação foi clicada.

Consegui fazer isso: a) definindo canShowCallout como NO b) subclassificando, MKPinAnnotationView e substituindo os métodos touchesBegan e touchesEnd.

Nota: Você precisa lidar com os eventos de toque para MKAnnotationView e não MKMapView

Shivaraj Tenginakai
fonte
3

Acabei de apresentar uma abordagem, a ideia aqui é

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

Aqui - testview é um UIViewtamanho de 320x100 - showCallout é BOOL - [self animateIn];é a função que faz a visualização de animação UIAlertView.

Nikesh K
fonte
O que selectedCoordinate especifica? Que tipo de objeto é esse?
Pradeep Reddy Kypa,
é o objeto CLLocationCoordinate2d.
bharathi kumar
1

Você pode usar leftCalloutView, definindo annotation.text como @ ""

Encontre abaixo o código de exemplo:

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
fonte
1
Isso vai 'funcionar' até certo ponto, mas não responde realmente à pergunta mais geral feita e é muito hackeado.
Cameron Lowell Palmer
1

Eu empurrei minha bifurcação do excelente SMCalloutView que resolve o problema com o fornecimento de uma visualização personalizada para textos explicativos e permitindo larguras / alturas flexíveis de forma bastante indolor. Ainda há algumas peculiaridades para resolver, mas é bastante funcional até agora:

https://github.com/u10int/calloutview

u10int
fonte