Подтвердить что ты не робот

Пользовательский вызывной пузырь MKAnnation с кнопкой

Я разрабатываю приложение, где пользователь локализуется через gps, и затем его спрашивают, находится ли он в определенном месте. Чтобы подтвердить это, пузырь вызова выдается ему сразу, спрашивая его, находится ли он в определенном месте.

Так как есть много подобных вопросов, я смог сделать настраиваемый пузырь вызова: Custom callout bubble

Моя проблема: кнопка не является "кликабельна" Мое предположение: потому что эта настраиваемая выноска выше, чем стандартный пузырь вызова, мне пришлось поместить его в отрицательный "кадр", поэтому кнопка не может быть нажата. Вот мой метод didSelectAnnotationView

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
    if(![view.annotation isKindOfClass:[MKUserLocation class]]) {
        CalloutView *calloutView = (CalloutView *)[[[NSBundle mainBundle] loadNibNamed:@"callOutView" owner:self options:nil] objectAtIndex:0];
        CGRect calloutViewFrame = calloutView.frame;
        calloutViewFrame.origin = CGPointMake(-calloutViewFrame.size.width/2 + 15, -calloutViewFrame.size.height);
        calloutView.frame = calloutViewFrame;
        [calloutView.calloutLabel setText:[(MyLocation*)[view annotation] title]];
        [calloutView.btnYes addTarget:self
                               action:@selector(checkin)
                     forControlEvents:UIControlEventTouchUpInside];
        calloutView.userInteractionEnabled = YES;
        view.userInteractionEnabled = YES;
        [view addSubview:calloutView];
    }

}

CalloutView - это просто класс с двумя свойствами (метка, которая показывает название места и кнопки) и с xib.

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

Моя следующая попытка заключалась в том, чтобы найти то, что было проще, чем асинхронные решения, и изменить его для моего использования. Вот как я нашел настраиваемую выноску tochi.

Tochi's custom callout

Основываясь на его работе, я смог настроить свой пузырь и изменить информационную кнопку для моей пользовательской кнопки. Моя проблема, однако, осталась прежней. Чтобы разместить мой пользовательский вид выноски поверх булавки, мне пришлось отдать его отрицательному кадру, поэтому моя кнопка была "кликабельна" только в нижних 5 пикселях. Кажется, что я должен, возможно, копать глубже в пузырь, вызываемый по умолчанию ios, подклассировать его и изменить рамку выноски там. Но сейчас я действительно безнадежен.

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

4b9b3361

Ответ 1

Существует несколько подходов к настройке выносок:

  • Самый простой способ - использовать существующие аксессуары для правого и левого выноса и поместить вашу кнопку в одну из них. Например:

    - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
        static NSString *identifier = @"MyAnnotationView";
    
        if ([annotation isKindOfClass:[MKUserLocation class]]) {
            return nil;
        }
    
        MKPinAnnotationView *view = (id)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
        if (view) {
            view.annotation = annotation;
        } else {
            view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
            view.canShowCallout = true;
            view.animatesDrop = true;
            view.rightCalloutAccessoryView = [self yesButton];
        }
    
        return view;
    }
    
    - (UIButton *)yesButton {
        UIImage *image = [self yesButtonImage];
    
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake(0, 0, image.size.width, image.size.height); // don't use auto layout
        [button setImage:image forState:UIControlStateNormal];
        [button addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventPrimaryActionTriggered];
    
        return button;
    }
    
    - (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
        NSLog(@"%s", __PRETTY_FUNCTION__);
    }
    

    Это дает:

    введите описание изображения здесь

  • Если вам действительно не нравится кнопка справа, где обычно идут аксессуары, вы можете отключить этот аксессуар, а iOS 9 дает возможность указать detailCalloutAccessoryView, который заменяет субтитры выносок на любой вид, который вы хотите:

    - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
        static NSString *identifier = @"MyAnnotationView";
    
        if ([annotation isKindOfClass:[MKUserLocation class]]) {
            return nil;
        }
    
        MKPinAnnotationView *view = (id)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
        if (view) {
            view.annotation = annotation;
        } else {
            view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
            view.canShowCallout = true;
            view.animatesDrop = true;
        }
        view.detailCalloutAccessoryView = [self detailViewForAnnotation:annotation];
    
        return view;
    }
    
    - (UIView *)detailViewForAnnotation:(PlacemarkAnnotation *)annotation {
        UIView *view = [[UIView alloc] init];
        view.translatesAutoresizingMaskIntoConstraints = false;
    
        UILabel *label = [[UILabel alloc] init];
        label.text = annotation.placemark.name;
        label.font = [UIFont systemFontOfSize:20];
        label.translatesAutoresizingMaskIntoConstraints = false;
        label.numberOfLines = 0;
        [view addSubview:label];
    
        UIButton *button = [self yesButton];
        [view addSubview:button];
    
        NSDictionary *views = NSDictionaryOfVariableBindings(label, button);
    
        [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[label]|" options:0 metrics:nil views:views]];
        [view addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
        [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[label]-[button]|" options:0 metrics:nil views:views]];
    
        return view;
    }
    
    - (UIButton *)yesButton {
        UIImage *image = [self yesButtonImage];
    
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.translatesAutoresizingMaskIntoConstraints = false; // use auto layout in this case
        [button setImage:image forState:UIControlStateNormal];
        [button addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventPrimaryActionTriggered];
    
        return button;
    }
    

    Это дает:

    введите описание изображения здесь

  • Если вы действительно хотите самостоятельно разработать настраиваемую выноску, Руководство по программированию местоположения и карт описывает шаги:

    В приложении iOS рекомендуется использовать метод делегата mapView:annotationView:calloutAccessoryControlTapped: для ответа, когда пользователи нажимают на элемент управления представлениями (пока элемент управления является потомком UIControl). В вашей реализации этого метода вы можете узнать личность представления аннотаций видов выносок, чтобы вы знали, какую аннотацию пользователь прослушивал. В приложении Mac приложение просмотра представлений точек вызова может реализовать метод действия, который отвечает, когда пользователь нажимает элемент управления в виде выноска.

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

    • Создайте подкласс NSView или UIView, который представляет собой настраиваемую выноску. Вероятно, что подкласс должен реализовать метод drawRect: для рисования вашего пользовательского контента.

    • Создайте контроллер представления, который инициализирует представление выноска и выполняет действие, связанное с кнопкой.

    • В представлении аннотации выполните hitTest: для ответа на хиты, которые находятся за пределами границ аннотаций, но внутри границ разрешений выносок, как показано в листинге 6-7.

    • В представлении аннотации внедряйте setSelected:animated:, чтобы добавить ваше представление выноски в качестве подвидности аннотационного представления, когда пользователь щелкает или забирает его.

    • Если вид выноска уже отображается, когда пользователь его выбирает, метод setSelected: должен удалить подзону callout из представления аннотации (см. листинг 6-8).

    • В представлении аннотаций initWithAnnotation: установите для свойства canShowCallout значение NO, чтобы карта не отображала стандартную выноску, когда пользователь выбирает аннотацию.

    Пока он в Swift, https://github.com/robertmryan/CustomMapViewAnnotationCalloutSwift иллюстрирует пример того, как вы можете выполнить эту полную настройку выноски (например, изменить форму выноски пузырь, изменение цвета фона и т.д.).

  • В предыдущем пункте описываются довольно сложные сценарии (т.е. вам нужно написать свой собственный код для обнаружения ответвлений за пределами представления, чтобы отменить его). Если вы поддерживаете iOS 9, вы можете просто использовать контроллер просмотра popover, например:

    - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
        static NSString *identifier = @"MyAnnotationView";
    
        if ([annotation isKindOfClass:[MKUserLocation class]]) {
            return nil;
        }
    
        MKPinAnnotationView *view = (id)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
        if (view) {
            view.annotation = annotation;
        } else {
            view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
            view.canShowCallout = false;  // note, we're not going to use the system callout
            view.animatesDrop = true;
        }
    
        return view;
    }
    
    - (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
        PopoverController *controller = [self.storyboard instantiateViewControllerWithIdentifier:@"AnnotationPopover"];
        controller.modalPresentationStyle = UIModalPresentationPopover;
    
        controller.popoverPresentationController.sourceView = view;
    
        // adjust sourceRect so it centered over the annotation
    
        CGRect sourceRect = CGRectZero;
        sourceRect.origin.x += [mapView convertCoordinate:view.annotation.coordinate toPointToView:mapView].x - view.frame.origin.x;
        sourceRect.size.height = view.frame.size.height;
        controller.popoverPresentationController.sourceRect = sourceRect;
    
        controller.annotation = view.annotation;
    
        [self presentViewController:controller animated:TRUE completion:nil];
    
        [mapView deselectAnnotation:view.annotation animated:true];  // deselect the annotation so that when we dismiss the popover, the annotation won't still be selected
    }
    

Ответ 2

Я пришел к другому подходу. Я пробовал другие, но они казались раздутыми, и я не хотел добавлять больше классов или полагаться на MKMapViewDelegate для обработки взаимодействия.

Вместо этого я переопределяю setSelected: анимированный из моего подкласса MKAnnotationView. Хитрость заключается в расширении границ аннотацииView после того, как она выбрана, чтобы полностью охватить представление вызова, а затем вернуть их в нормальное состояние после его отмены. Это позволит вашим пользовательским вызовам принимать прикосновения и взаимодействия вне исходных границ MKAnnotationView.

Вот пример обрезанного кода, чтобы начать работу:

#define kAnnotationCalloutBoxTag    787801
#define kAnnotationCalloutArrowTag  787802
#define kAnnotationTempImageViewTag 787803

-(void)setSelected:(BOOL)selected animated:(BOOL)animated
{
    if (selected == self.selected)
    {
        NSLog(@"annotation already selected, abort!");
        return;
    }
    if (selected)
    {
        self.image = nil; //hide default image otherwise it takes the shape of the entire bounds

        UIView* calloutBox = [self newCustomCallout];
        float imgW = [self unselectedSize].width;
        float imgH = [self unselectedSize].height;
        float arrowW = 20;
        float arrowH = 12;

        //Annotation frames wrap a center coordinate, in this instance we want the call out box to fall under the
        //central coordinate, so we need to adjust the height to be double what the callout box height would be
        //making the height *2, this is to make sure the callout view is inside of it.
        self.bounds = CGRectMake(0, 0, calloutBox.frame.size.width, calloutBox.frame.size.height*2 + arrowH*2 + imgH);
        CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);

        UIView* imgView = [[UIImageView alloc] initWithImage:icon];
        [imgView setFrame:CGRectMake(center.x - imgW/2, center.y-imgH/2, imgW, imgH)];
        imgView.tag = kAnnotationTempImageViewTag;
        [self addSubview:imgView];

        UIView* triangle = [self newTriangleViewWithFrame:CGRectMake(center.x-arrowW/2, center.y+imgH/2, arrowW, arrowH)];
        triangle.tag = kAnnotationCalloutArrowTag;
        [self addSubview:triangle];

        [calloutBox setFrame:CGRectMake(0, center.y+imgH/2+arrowH, calloutBox.width, calloutBox.height)];
        calloutBox.tag = kAnnotationCalloutBoxTag;
        [self addSubview:calloutBox];
    }
    else
    {
        //return things back to normal
        UIView* v = [self viewWithTag:kAnnotationCalloutBoxTag];
        [v removeFromSuperview];
        v = [self viewWithTag:kAnnotationCalloutArrowTag];
        [v removeFromSuperview];
        v = [self viewWithTag:kAnnotationTempImageViewTag];
        [v removeFromSuperview];

        self.image = icon;
        self.bounds = CGRectMake(0, 0, [self unselectedSize].width, [self unselectedSize].height);
    }
    [super setSelected:selected animated:animated];
}

-(CGSize)unselectedSize
{
    return CGSizeMake(20,20);
}

-(UIView*)newCustomCallout
{
    //create your own custom call out view
    UIView* v = [[UIView alloc] initWithFrame:CGRectMake(0,0,250,250)];
    v.backgroundColor = [UIColor greenColor];
    return v;
}

-(UIView*)newTriangleWithFrame:(CGRect)frame
{
    //create your own triangle
    UIImageView* v = [[UIImageView alloc] initWithFrame:frame];
    [v setImage:[UIImage imageNamed:@"trianglePointedUp.png"]];
    return v;
}

Ответ 3

(void)mapView:(MKMapView *)mapViewIn didSelectAnnotationView:(MKAnnotationView *)view {
    if(![view.annotation isKindOfClass:[MKUserLocation class]])
    {
        CustomeCalloutViewController *calloutView = [[CustomeCalloutViewController alloc]initWithNibName:@"CustomeCalloutViewController" bundle:nil];
        [calloutView setPopinTransitionStyle:BKTPopinTransitionStyleSlide];
        [calloutView setPopinTransitionDirection:BKTPopinTransitionDirectionTop];
        [self presentPopinController:calloutView animated:YES completion:^{
            NSLog(@"Popin presented !");
        }];
        [mapView deselectAnnotation:view.annotation animated:true];
    }
}