Как настроить выноску для MKAnnotationView?


88

Я сейчас работаю с мапкитом и застрял.

У меня есть настраиваемое представление аннотаций, которое я использую, и я хочу использовать свойство изображения для отображения точки на карте с моим собственным значком. У меня это работает нормально. Но я также хотел бы переопределить представление выноски по умолчанию (всплывающее окно с заголовком / подзаголовком при прикосновении к значку аннотации). Я хочу иметь возможность управлять самой выноской: mapkit предоставляет доступ только к левому и правому вспомогательным представлениям выноски, но не имеет возможности предоставить настраиваемый вид для пузыря выноски, или дать ему нулевой размер, или что-то еще.

Моя идея заключалась в том, чтобы переопределить selectAnnotation / deselectAnnotation в моем MKMapViewDelegate, а затем нарисовать свое собственное представление, вызвав мое настраиваемое представление аннотации. Это работает, но только тогда , когда canShowCalloutбудет установлен YESв моем пользовательском классе просмотра аннотаций. Эти методы НЕ вызываются, если у меня установлено значение NO(это то, что я хочу, чтобы пузырь выноски по умолчанию не рисовался). Поэтому у меня нет возможности узнать, коснулся ли пользователь моей точки на карте (выбрал ее) или коснулся точки, которая не является частью моих представлений аннотаций (отклонила ее), не отображая вид пузыря выноски по умолчанию.

Я попытался пойти другим путем и просто обрабатывать все события касания на карте самостоятельно, и мне кажется, что это не работает. Я читал другие сообщения, связанные с отслеживанием событий касания на карте, но это не совсем то, что мне нужно. Есть ли способ покопаться в представлении карты, чтобы удалить пузырь выноски перед рисованием? Я в растерянности.

Какие-либо предложения? Я упускаю что-то очевидное?


Эта ссылка не работает, но нашел здесь тот же пост -> Создание пользовательских
Фед Мика

2
Вы можете обратиться к этому проекту для быстрой демонстрации. github.com/akshay1188/CustomAnnotation Компиляция ответов выше в одну работающую демонстрацию.
akshay1188 03

Ответы:


54

Есть еще более простое решение.

Создайте заказ UIView(для вашего уточнения).

Затем создайте подкласс MKAnnotationViewи переопределите setSelectedследующим образом:

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

Бум, работа сделана.


8
привет TappCandy! Во-первых, спасибо за ваше решение. Это работает, но при загрузке представления (я делаю это из файла пера) кнопки не работают. Что я могу сделать, чтобы они работали нормально? Спасибо
Frade

Мне нравится простота этого решения!
Эрик Бротто

6
Хм - не работает! Он заменяет аннотацию карты (то есть булавку), а НЕ выноску "пузырь".
PapillonUK

2
Это не сработает. MKAnnotationView не имеет ссылки на представление карты, поэтому у вас есть несколько проблем с добавлением настраиваемой выноски. Например, вы не знаете, будет ли добавление этого в качестве подпредставления за кадром.
Кэмерон Лоуэлл Палмер

@PapillonUK У вас есть контроль над рамкой выноски. Это не заменяет штифт, он появляется поверх него. Отрегулируйте его положение, когда вы добавляете его как подвид.
Бен Паккард,

40

detailCalloutAccessoryView

Раньше это было проблемой , но Apple решила эту проблему, просто проверьте документацию на MKAnnotationView.

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

Действительно, вот и все. Принимает любой UIView.


Какой лучше использовать?
Satyam

13

Продолжая блестяще простой ответ @ TappCandy, если вы хотите анимировать пузырь так же, как и по умолчанию, я создал этот метод анимации:

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

Это выглядит довольно сложно, но если точка выноски спроектирована так, чтобы она располагалась по центру внизу, вы можете просто заменить myBubbleWidth и myBubbleHeightс вашим собственным размером для его работы. И не забудьте убедиться, что для ваших подвидов autoResizeMaskсвойство установлено на 63 (т.е. «все»), чтобы они правильно масштабировались в анимации.

:-Джо


Кстати, вы можете анимировать по свойству scale, а не по кадру, чтобы получить правильное масштабирование содержимого.
occulus

Не используйте CGRectMake. Используйте CGTransform.
Кэмерон Лоуэлл Палмер

@occulus - Думаю, я попробовал это первым, но у меня возникли проблемы с рендерингом в iOS 4. Но это могло быть из-за того, что я не использовал CABasicAnimation... Слишком давно, чтобы вспомнить! Я знаю, что мне придется анимировать свойство y из-за смещения центра.
jowie

@CameronLowellPalmer - почему бы не использовать CGRectMake?
jowie

@jowie Потому что синтаксис лучше и позволяет избежать магических значений в вашем коде. CGAffineTransformMakeScale (1.1f, 1.1f); Увеличить коробку на 110% понятно. То, как вы это сделали, может сработать, но это некрасиво.
Кэмерон Лоуэлл Палмер,

8

Я считаю, что это лучшее решение для меня. Вам придется проявить немного творчества, чтобы сделать свои собственные настройки

В своем MKAnnotationViewподклассе вы можете использовать

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

А если subviewэто a UICalloutView, то с ним можно поиграть, а что внутри.


1
как проверить, является ли подпредставление UICalloutView, поскольку UICalloutview не является общедоступным классом.
Маниш Ахуджа

if ([[[subview class] description] isEqualToString:@"UICalloutView"]) Думаю, есть способ получше, но он работает.
яοвοτағτєяа ււ

были ли у вас проблемы с этим, поскольку, как указал Маниш, это частный класс.
Дэниел

Вероятно, они изменили структуру UIView для calloutviews. Я не обновлял приложение, которое использует это, так что вы сами: P
яοвοτағτєяа ււ

6

У меня такая же проблема. В этом блоге http://spitzkoff.com/craig/?p=81 есть множество сообщений на эту тему .

Простое использование здесь MKMapViewDelegateне помогает, а создание подклассов MKMapViewи попытки расширить существующие функциональные возможности также не помогли мне.

В итоге я создал свой собственный, CustomCalloutViewкоторый у меня есть поверх моих MKMapView. Вы можете стилизовать это представление по своему усмотрению.

У меня CustomCalloutViewесть метод, похожий на этот:


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

Требуется MKAnnotation объект и устанавливает свой собственный заголовок, после чего вызывает два других довольно уродливых метода, которые регулируют ширину и размер содержимого выноски, а затем рисуют вокруг него речевой пузырь в правильном положении.

Наконец, представление добавляется в качестве подвида к mapView. Проблема с этим решением заключается в том, что трудно удерживать выноску в правильном положении при прокрутке карты. Я просто скрываю выноску в методе делегата представлений карты при изменении региона, чтобы решить эту проблему.

На решение всех этих проблем ушло время, но теперь выноска ведет себя почти как официальная, но у меня она в моем стиле.


Второе: UICalloutView - это частный класс, официально не доступный в SDK. Лучше всего последовать совету Саши.
JoePasq

Привет, Саша! Благодарим вас за ответ. Но текущая проблема, с которой мы сталкиваемся, заключается в том, как получить позицию (x, y, а не широту или долготу) на карте, где появляются булавки, чтобы мы могли отобразить представление выноски.
Зак,

1
преобразование координат может быть легко выполнено двумя функциями MKMapView: # - convertCoordinate: toPointToView: # - convertPoint: toCoordinateFromView:
Саша

5

В основном, чтобы решить эту проблему, необходимо: a) Не допускать появления пузыря выноски по умолчанию. б) Выясните, какая аннотация была нажата.

Я смог добиться этого: а) установив для canShowCallout значение NO; б) создав подклассы, MKPinAnnotationView и переопределив методы touchesBegan и touchesEnd.

Примечание. Вам необходимо обрабатывать события касания для MKAnnotationView, а не для MKMapView.


3

Я просто придумал подход, идея здесь

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

Здесь - testview имеет UIViewразмер 320x100 - showCallout - BOOL - [self animateIn];это функция, которая просматривает анимацию как UIAlertView.


Что указывает selectedCoordinate? Что это за объект?
Прадип Редди Кипа

это объект CLLocationCoordinate2d.
bharathi kumar

1

Вы можете использовать leftCalloutView, задав для annotation.text значение @ ""

Ниже приведен пример кода:

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
В какой-то степени это «сработает», но на самом деле не отвечает на более общий вопрос, и это очень хакерский прием.
Кэмерон Лоуэлл Палмер

1

Я выпустил свой форк превосходного SMCalloutView, который решает проблему, предоставляя настраиваемый вид для выносок и довольно безболезненно позволяя гибкую ширину / высоту. По-прежнему нужно проработать некоторые причуды, но пока он довольно функциональный:

https://github.com/u10int/calloutview

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.