¿Cómo personalizar la burbuja de llamada para MKAnnotationView?

88

Actualmente estoy trabajando con el mapkit y estoy atascado.

Tengo una vista de anotación personalizada que estoy usando y quiero usar la propiedad de la imagen para mostrar el punto en el mapa con mi propio ícono. Tengo esto funcionando bien. Pero lo que también me gustaría hacer es anular la vista de llamada predeterminada (la burbuja que aparece con el título / subtítulo cuando se toca el icono de anotación). Quiero poder controlar la llamada en sí: el mapkit solo proporciona acceso a las vistas de llamada auxiliares izquierda y derecha, pero no hay forma de proporcionar una vista personalizada para la burbuja de llamada, o darle tamaño cero, o cualquier otra cosa.

Mi idea era anular selectAnnotation / deselectAnnotation en my MKMapViewDelegate, y luego dibujar mi propia vista personalizada haciendo una llamada a mi vista de anotación personalizada. Esto funciona, pero solo cuando canShowCalloutestá configurado YESen mi clase de vista de anotación personalizada. Estos métodos NO se llaman si tengo esto configurado en NO(que es lo que quiero, para que no se dibuje la burbuja de llamada predeterminada). Por lo tanto, no tengo forma de saber si el usuario tocó mi punto en el mapa (lo seleccionó) o tocó un punto que no es parte de mis vistas de anotaciones (lo eliminó) sin que aparezca la vista de burbuja de llamada predeterminada.

Intenté ir por un camino diferente y manejar todos los eventos táctiles yo mismo en el mapa, y parece que no puedo hacer que esto funcione. Leí otras publicaciones relacionadas con la captura de eventos táctiles en la vista del mapa, pero no son exactamente lo que quiero. ¿Hay alguna forma de profundizar en la vista del mapa para eliminar la burbuja de llamada antes de dibujar? Estoy perdido.

¿Alguna sugerencia? ¿Me estoy perdiendo algo obvio?

Zach
fuente
Este enlace no funciona, pero encontré la misma publicación aquí -> Creación de llamadas de anotación de mapas personalizados - Parte 1
Fede Mika
2
Puede consultar este proyecto para obtener una demostración rápida. github.com/akshay1188/CustomAnnotation Compilación de las respuestas anteriores en una demostración en ejecución.
akshay1188

Respuestas:

54

Existe una solución aún más sencilla.

Cree un personalizado UIView(para su texto destacado).

Luego cree una subclase de MKAnnotationViewy anule de la setSelectedsiguiente manera:

- (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, trabajo hecho.

TappCandy
fuente
8
hola TappCandy! Primero, gracias por tu solución. Funciona, pero cuando se carga una vista (lo estoy haciendo desde un archivo nib) los botones no funcionan. ¿Qué puedo hacer para que funcionen correctamente? Gracias
Frade
¡Amo la simplicidad de esta solución!
Eric Brotto
6
Hmm, ¡esto no funciona! Reemplaza la anotación del mapa (es decir, el alfiler) NO la llamada "burbuja".
PapillonUK
2
Esto no funcionará. MKAnnotationView no tiene una referencia a la vista de mapa, por lo que tiene varios problemas al agregar una llamada personalizada. Por ejemplo, no sabe si agregar esto como una subvista estará fuera de la pantalla.
Cameron Lowell Palmer
@PapillonUK Tienes control sobre el marco de la llamada. No reemplaza el pasador, aparece encima de él. Ajuste su posición cuando la agregue como una subvista.
Ben Packard
40

detailCalloutAccessoryView

En los viejos tiempos esto era un fastidio, pero Apple lo ha resuelto, solo revisa los documentos en MKAnnotationView

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

Realmente, eso es todo. Toma cualquier UIView.

Cameron Lowell Palmer
fuente
¿Cuál es mejor para usar?
Satyam
13

Continuando con la brillante y simple respuesta de @ TappCandy, si desea animar su burbuja de la misma manera que la predeterminada, he producido este método de animación:

- (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, pero siempre que la punta de su burbuja de llamada esté diseñada para estar en el centro de la parte inferior, debería poder reemplazarla myBubbleWidthy usar myBubbleHeightsu propio tamaño para que funcione. Y recuerde asegurarse de que sus subvistas tengan su autoResizeMaskpropiedad establecida en 63 (es decir, "todas") para que escalen correctamente en la animación.

: -Joe

jowie
fuente
Por cierto, podría animar en la propiedad de escala en lugar del marco, para obtener la escala adecuada del contenido.
occulus
No use CGRectMake. Utilice CGTransform.
Cameron Lowell Palmer
@occulus: creo que lo intenté primero, pero tuve problemas de renderizado en iOS 4. Pero eso podría haber sido porque no estaba usando CABasicAnimation... Sé que también tendría que animar la propiedad y, debido al desplazamiento del centro.
jowie
@CameronLowellPalmer: ¿por qué no usarlo CGRectMake?
jowie
@jowie Porque la sintaxis es mejor y evita los valores mágicos en su código. CGAffineTransformMakeScale (1.1f, 1.1f); Hacer crecer la caja en un 110% está claro. La forma en que lo hizo podría funcionar, pero no es bonita.
Cameron Lowell Palmer
8

Descubrí que esta es la mejor solución para mí. Tendrás que usar un poco de creatividad para hacer tus propias personalizaciones.

En su MKAnnotationViewsubclase, puede 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++;
            }
        }
    }
}

Y si subviewes un UICalloutView, entonces puedes jugar con él y lo que hay dentro.

яοвοτағτєяа ււ
fuente
1
¿Cómo se comprueba si la subvista es una UICalloutView ya que UICalloutview no es una clase pública?
Manish Ahuja
if ([[[subview class] description] isEqualToString:@"UICalloutView"]) Creo que hay una forma mejor, pero funciona.
яοвοτағτєяа ււ
¿Tuviste algún problema con esto? Como señaló Manish, es una clase privada.
Daniel
Probablemente cambiaron la estructura de UIView para las vistas de llamada. No he actualizado la aplicación que usa esto, así que estás solo: P
яοвοτағτєяа ււ
6

Yo tuve el mismo problema. Hay una serie de publicaciones de blog sobre este tema en este blog http://spitzkoff.com/craig/?p=81 .

El solo uso de MKMapViewDelegateno te ayuda aquí y la subclasificación MKMapViewy el intento de extender la funcionalidad existente tampoco funcionó para mí.

Lo que terminé haciendo es crear el mío propio CustomCalloutViewque tengo encima del mío MKMapView. Puede diseñar esta vista de la forma que desee.

Mi CustomCalloutViewtiene un método similar 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];
}

Toma un MKAnnotationobjeto y establece su propio título, luego llama a otros dos métodos que son bastante feos que ajustan el ancho y el tamaño del contenido de la llamada y luego dibujan el bocadillo a su alrededor en la posición correcta.

Finalmente, la vista se agrega como una subvista a mapView. El problema con esta solución es que es difícil mantener la llamada en la posición correcta cuando se desplaza la vista del mapa. Solo estoy ocultando la llamada en el método delegado de vistas de mapa en un cambio de región para resolver este problema.

Me tomó algún tiempo resolver todos esos problemas, pero ahora el anuncio casi se comporta como el oficial, pero lo tengo en mi propio estilo.

Sascha Konietzke
fuente
Secundo eso: UICalloutView es una clase privada y no está disponible en el SDK oficialmente. Tu mejor opción es seguir los consejos de Sascha.
JoePasq
Hola Sascha, gracias por tu respuesta. Pero, el problema actual que estamos experimentando es cómo obtener la posición (x, y y no lat o long) en el mapa donde aparecen los pines, de modo que podamos mostrar la vista de llamada.
Zach
1
La conversión de coordenadas se puede realizar fácilmente mediante dos funciones de MKMapView: # - convertCoordinate: toPointToView: # - convertPoint: toCoordinateFromView:
Sascha Konietzke
5

Básicamente, para resolver esto, es necesario: a) Evitar que aparezca la burbuja de llamada predeterminada. b) Averigüe en qué anotación se hizo clic.

Pude lograrlos: a) estableciendo canShowCallout en NO b) subclasificando, MKPinAnnotationView y anulando los métodos touchesBegan y touchesEnd.

Nota: debe manejar los eventos táctiles para MKAnnotationView y no MKMapView

Shivaraj Tenginakai
fuente
3

Se me ocurrió un enfoque, la idea aquí es

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

Aquí, la vista de prueba tiene un UIViewtamaño de 320x100, showCallout es BOOL, [self animateIn];es la función que visualiza la animación como UIAlertView.

Nikesh K
fuente
¿Qué especifica selectedCoordinate? ¿Qué tipo de objeto es ese?
Pradeep Reddy Kypa
es el objeto CLLocationCoordinate2d.
bharathi kumar
1

Puede usar leftCalloutView, configurando annotation.text en @ ""

A continuación, encontrará el código de ejemplo:

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
fuente
1
Esto 'funcionará' hasta cierto punto, pero en realidad no responde a la pregunta más general que se hace y es muy hack.
Cameron Lowell Palmer
1

He sacado mi bifurcación del excelente SMCalloutView que resuelve el problema al proporcionar una vista personalizada para las llamadas y permitir anchos / alturas flexibles sin dolor. Aún quedan algunas peculiaridades por resolver, pero hasta ahora es bastante funcional:

https://github.com/u10int/calloutview

u10int
fuente