Jak dostosować dymek objaśnienia dla MKAnnotationView?

88

Obecnie pracuję z mapkitem i utknąłem.

Mam niestandardowy widok adnotacji, którego używam, i chcę użyć właściwości obrazu, aby wyświetlić punkt na mapie z własną ikoną. Mam to działa dobrze. Ale chciałbym również zastąpić domyślny widok objaśnienia (dymek, który pojawia się z tytułem / podtytułem po dotknięciu ikony adnotacji). Chcę mieć możliwość kontrolowania samego objaśnienia: zestaw map zapewnia dostęp tylko do lewego i prawego pomocniczego widoku objaśnień, ale nie ma możliwości zapewnienia niestandardowego widoku dla dymka objaśnienia, nadania mu zerowego rozmiaru lub czegokolwiek innego.

Mój pomysł polegał na nadpisaniu selectAnnotation / deselectAnnotation w moim MKMapViewDelegate, a następnie narysowaniu własnego widoku niestandardowego, wywołując mój niestandardowy widok adnotacji. To działa, ale tylko wtedy, gdy canShowCalloutjest ustawione na YESw mojej niestandardowej klasie widoku adnotacji. Te metody NIE są wywoływane, jeśli ustawię to na NO(co jest tym, czego chcę, aby domyślny bąbelek wywołania nie był rysowany). Nie mam więc możliwości dowiedzenia się, czy użytkownik dotknął mojego punktu na mapie (zaznaczył go) lub dotknął punktu, który nie jest częścią moich widoków adnotacji (usunął go) bez wyświetlania domyślnego widoku dymka objaśnień.

Próbowałem pójść inną ścieżką i po prostu sam obsługiwać wszystkie zdarzenia dotykowe na mapie, ale nie wydaje mi się, aby to działało. Czytałem inne posty związane z łapaniem zdarzeń dotykowych w widoku mapy, ale nie są one dokładnie tym, czego chcę. Czy istnieje sposób, aby zagłębić się w widok mapy, aby usunąć dymek objaśnienia przed rysowaniem? Jestem zagubiony.

Jakieś sugestie? Czy brakuje mi czegoś oczywistego?

Zach
źródło
Ten link nie działa, ale znalazłem ten sam post tutaj -> Tworzenie niestandardowych objaśnień adnotacji mapy - Część 1
Fede Mika
2
Możesz odwołać się do tego projektu, aby uzyskać szybkie demo. github.com/akshay1188/CustomAnnotation Kompilacja powyższych odpowiedzi w jedno działające demo.
akshay1188

Odpowiedzi:

54

Jest jeszcze prostsze rozwiązanie.

Utwórz niestandardowy UIView(dla swojego objaśnienia).

Następnie utwórz podklasę MKAnnotationViewi nadpisz setSelectedw następujący sposób:

- (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...
    }
}

Bum, praca wykonana.

TappCandy
źródło
8
cześć TappCandy! Po pierwsze, dziękuję za rozwiązanie. Działa, ale kiedy ładuję widok (robię to z pliku nib) przyciski nie działają. Co mogę zrobić, aby działały poprawnie? Dzięki
Frade
Uwielbiam prostotę tego rozwiązania!
Eric Brotto
6
Hmm - to nie działa! Zastępuje adnotację mapy (np. Pinezkę), a NIE objaśnienie „dymek”.
PapillonUK
2
To nie zadziała. MKAnnotationView nie ma odniesienia do widoku mapy, więc masz kilka problemów z dodaniem niestandardowego odwołania. Na przykład nie wiesz, czy dodanie tego jako widoku podrzędnego będzie poza ekranem.
Cameron Lowell Palmer
@PapillonUK Masz kontrolę nad ramką wywołania. Nie zastępuje szpilki, pojawia się na niej. Dostosuj jego położenie, gdy dodasz go jako widok podrzędny.
Ben Packard,
40

detailCalloutAccessoryView

W dawnych czasach było to uciążliwe, ale Apple to rozwiązało, po prostu sprawdź dokumentację na MKAnnotationView

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

Naprawdę, to wszystko. Pobiera dowolny UIView.

Cameron Lowell Palmer
źródło
Którego najlepiej używać?
Satyam,
13

Kontynuując genialnie prostą odpowiedź @ TappCandy, jeśli chcesz animować swój bąbelek w taki sam sposób, jak domyślny, stworzyłem tę metodę animacji:

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

Wygląda to na dość skomplikowane, ale tak długo, jak punkt Twojego dymka objaśnienia jest zaprojektowany tak, aby znajdował się pośrodku dołu, powinieneś mieć możliwość wymiany myBubbleWidthi myBubbleHeightwłasnego rozmiaru, aby działał. I pamiętaj, aby upewnić się, że podglądy mają swoją autoResizeMaskwłaściwość ustawioną na 63 (tj. „Wszystkie”), aby były prawidłowo skalowane w animacji.

: -Joe

jowie
źródło
Przy okazji, możesz animować właściwość scale zamiast ramki, aby uzyskać odpowiednie skalowanie zawartości.
occulus
Nie używaj CGRectMake. Użyj CGTransform.
Cameron Lowell Palmer
@occulus - myślę, że spróbowałem tego jako pierwszy, ale miałem problemy z renderowaniem w iOS 4. Ale mogło to być spowodowane tym, że nie używałem CABasicAnimation... Zbyt dawno temu, żeby pamiętać! Wiem, że musiałbym również animować właściwość y ze względu na przesunięcie środka.
jowie
@CameronLowellPalmer - dlaczego nie używać CGRectMake?
jowie
@jowie Ponieważ składnia jest lepsza i pozwala uniknąć magicznych wartości w kodzie. CGAffineTransformMakeScale (1.1f, 1.1f); Powiększenie pudełka o 110% jest jasne. Sposób, w jaki to zrobiłeś, może zadziałać, ale nie jest ładny.
Cameron Lowell Palmer
8

Okazało się, że jest to dla mnie najlepsze rozwiązanie. Będziesz musiał wykazać się kreatywnością, aby dokonać własnych dostosowań

W swojej MKAnnotationViewpodklasie możesz użyć

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

A jeśli subviewjest a UICalloutView, to możesz się z tym pieprzyć i co jest w środku.

яοвοτағτєяа ււ
źródło
1
jak sprawdzić, czy widok podrzędny jest UICalloutView, ponieważ UICalloutview nie jest klasą publiczną.
Manish Ahuja,
if ([[[subview class] description] isEqualToString:@"UICalloutView"]) Myślę, że jest lepszy sposób, ale to działa.
яοвοτағτєяа ււ
czy miałeś z tym jakieś problemy, ponieważ jak zauważył Manish, są to zajęcia prywatne.
Daniel,
Prawdopodobnie zmienili strukturę UIView widoków wywołań. Nie zaktualizowałem aplikacji, która z tego korzysta, więc jesteś sam: P
яοвοτағτєяа ււ
6

Miałem ten sam problem. Na tym blogu http://spitzkoff.com/craig/?p=81 znajduje się wiele postów na ten temat .

Samo użycie MKMapViewDelegatenie pomoże ci tutaj, a tworzenie podklas MKMapViewi próba rozszerzenia istniejącej funkcjonalności również nie działa dla mnie.

Skończyło się na stworzeniu własnego, CustomCalloutViewktóry mam nad sobą MKMapView. Możesz stylizować ten widok w dowolny sposób.

My CustomCalloutViewma metodę podobną do tej:


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

Pobiera MKAnnotationobiekt i ustawia własny tytuł, a następnie wywołuje dwie inne metody, które są dość brzydkie, które dostosowują szerokość i rozmiar zawartości objaśnienia, a następnie rysują wokół niego dymek mowy we właściwej pozycji.

Na koniec widok jest dodawany jako widok podrzędny do mapView. Problem z tym rozwiązaniem polega na tym, że trudno jest utrzymać objaśnienie we właściwej pozycji podczas przewijania widoku mapy. Właśnie ukrywam objaśnienie w metodzie delegowania widoków mapy na zmianę regionu, aby rozwiązać ten problem.

Zajęło trochę czasu, aby rozwiązać wszystkie te problemy, ale teraz objaśnienie zachowuje się prawie jak oficjalne, ale mam je w swoim własnym stylu.

Sascha Konietzke
źródło
Po drugie: UICalloutView to klasa prywatna i nie jest oficjalnie dostępna w SDK. Najlepiej postępować zgodnie z radą Saschy.
JoePasq
Cześć Sascha, Dziękujemy za twoją odpowiedź. Jednak obecnym problemem, którego doświadczamy, jest to, jak uzyskać pozycję (x, y, a nie szerokość lub długość) na mapie, na której pojawiają się szpilki, abyśmy mogli wyświetlić widok objaśnienia.
Zach
1
konwersję współrzędnych można łatwo wykonać za pomocą dwóch funkcji MKMapView: # - convertCoordinate: toPointToView: # - convertPoint: toCoordinateFromView:
Sascha Konietzke
5

Zasadniczo, aby rozwiązać ten problem, należy: a) zapobiec wyświetlaniu domyślnego dymka objaśnienia. b) Dowiedz się, która adnotacja została kliknięta.

Udało mi się to osiągnąć poprzez: a) ustawienie canShowCallout na NO b) podklasę, MKPinAnnotationView i nadpisanie metod touchesBegan i touchesEnd.

Uwaga: musisz obsłużyć zdarzenia dotyku dla MKAnnotationView, a nie MKMapView

Shivaraj Tenginakai
źródło
3

Po prostu wymyśliłem podejście, chodzi o to

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

Tutaj - testview ma UIViewrozmiar 320x100 - showCallout to BOOL - [self animateIn];to funkcja, która wyświetla animację jak UIAlertView.

Nikesh K.
źródło
Co określa selectedCoordinate? Jaki to rodzaj obiektu?
Pradeep Reddy Kypa
jest to obiekt CLLocationCoordinate2d.
bharathi kumar
1

Możesz użyć leftCalloutView, ustawiając adnotation.text na @ ""

Poniżej przykładowy kod:

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
źródło
1
To zadziała do pewnego stopnia, ale tak naprawdę nie odpowiada na bardziej ogólne pytanie i jest bardzo hakerskie.
Cameron Lowell Palmer
1

Wypchnąłem widelec doskonałego SMCalloutView, który rozwiązuje problem z zapewnieniem niestandardowego widoku objaśnień i umożliwianiem elastycznych szerokości / wysokości dość bezboleśnie. Wciąż jest kilka dziwactw do rozwiązania, ale jak dotąd jest całkiem funkcjonalny:

https://github.com/u10int/calloutview

u10int
źródło