moujib
moujib

Reputation: 752

CustomAnnotationView with a button

I've been strunggling with an issue on my project :

I've a mapView and I have to show the annotation presented below (The small (+) is my button)

what's I have to do

I've subclassed MKAnnotationView and I have CustomAnnotationView.h like this :

@interface CustomAnnotationView : MKAnnotationView

@property (strong, nonatomic) UIImageView *calloutView;
@property (strong, nonatomic) UIButton *pinButton;
@property (strong, nonatomic) UIView *annView;

- (void)setSelected:(BOOL)selected animated:(BOOL)animated;
- (void)animateCalloutAppearance;

My CustomAnnotationView.m

- (void)setSelected:(BOOL)selected animated:(BOOL)animated{
    [super setSelected:selected animated:animated];


    if(selected)
    {
        //building my custom animation 
        annView = [[ UIView alloc ] initWithFrame:(CGRectMake(0, 0, 30, 30)) ];

        annView.frame =CGRectMake(0, 0, 200, 50);

        calloutView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"customcallout.png"]];


        [calloutView setFrame:CGRectMake(-25, 50, 0, 0)];
        [calloutView sizeToFit];


        [self animateCalloutAppearance];

        //I tried to add a button here but I could't do it like it should be
        [annView addSubview:calloutView];

        [self addSubview:annView];

    }
    else
    {
        //Remove your custom view...

        [annView removeFromSuperview];
    }
}

- (void)didAddSubview:(UIView *)subview{


        if ([[[subview class] description] isEqualToString:@"UICalloutView"]) {
            for (UIView *subsubView in subview.subviews) {
                if ([subsubView class] == [UIImageView class]) {
                    UIImageView *imageView = ((UIImageView *)subsubView);
                    [imageView removeFromSuperview];
                }else if ([subsubView class] == [UILabel class]) {
                    UILabel *labelView = ((UILabel *)subsubView);
                    [labelView removeFromSuperview];
                }
            }

    }
}


- (void)animateCalloutAppearance {
    CGFloat scale = 0.001f;
    calloutView.transform = CGAffineTransformMake(scale, 0.0f, 0.0f, scale, 0, -50);

    [UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationCurveEaseOut animations:^{
        CGFloat scale = 1.1f;
        calloutView.transform = CGAffineTransformMake(scale, 0.0f, 0.0f, scale, 0, 2);

    } completion:^(BOOL finished) {
        [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationCurveEaseInOut animations:^{
            CGFloat scale = 0.95;
            calloutView.transform = CGAffineTransformMake(scale, 0.0f, 0.0f, scale, 0, -2);

        } completion:^(BOOL finished) {
            [UIView animateWithDuration:0.075 delay:0 options:UIViewAnimationCurveEaseInOut animations:^{
                CGFloat scale = 1.0;
                calloutView.transform = CGAffineTransformMake(scale, 0.0f, 0.0f, scale, 0, 0);

            } completion:nil];
        }];
    }];
}

@end

With this I'm able to show my custom annotation on the map but I can't figure out how to place a button on it and this button should of course be able to respond to clicks like the callback calloutAccessoryControlTapped:

Please anyone with a working example code or idea. Thank you.

Upvotes: 3

Views: 1180

Answers (2)

Jack Dewhurst
Jack Dewhurst

Reputation: 349

I solved this using - (UIView*)hitTest in my CustmAnnotationView.m class. I have a separate nib for the callout view and use hittest to determine whether the UILabel named directionsButton (linked from the callout nib) was clicked:

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
    if (selected) {
        calloutVisible = YES;
        hitTestBuffer = NO;
        self.calloutView = [[[NSBundle mainBundle] loadNibNamed:@"CalloutView" owner:self options:nil] objectAtIndex:0];  
        [directionsButton setFont:[UIFont fontWithName:@"DIN-Medium" size:12.0f]];
        [calloutView setFrame:CGRectMake(calloutView.frame.origin.x, calloutView.frame.origin.y, 280, calloutView.frame.size.height)];          
        [calloutView setAlpha:0.0f];    
        [self addSubview:calloutView];
        [self sendSubviewToBack:calloutView];


        [UIView animateWithDuration:0.3f delay:0.0f options:0 animations:^{     
            [calloutView setAlpha:1.0f];
        } completion:^(BOOL finished) {
        }];

        [super setSelected:selected animated:animated];

    } else {
        calloutVisible = NO;

        CGRect newFrame = self.frame;
        newFrame.size.width = 52;
        self.frame = newFrame;

        [UIView animateWithDuration:0.3f delay:0.0f options:0 animations:^{
            [calloutView setAlpha:0.0f];
        } completion:^(BOOL finished) {
            [calloutView removeFromSuperview];
        }];
    }
}


- (void)directionsPressed
{
    if ([parent respondsToSelector:@selector(directionsPressed:)])
        [parent performSelector:@selector(directionsPressed:) withObject:self.stockist];
}


- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if (calloutVisible) {
        CGPoint hitPoint = [calloutView convertPoint:point toView:directionsButton];
        if ([calloutView pointInside:hitPoint withEvent:event]) {
            if (!hitTestBuffer) {
                [self directionsPressed];
                hitTestBuffer = YES;
            }
            return [directionsButton hitTest:point withEvent:event];
        } else {
            hitTestBuffer = NO;
        }
    }
    return [super hitTest:point withEvent:event];
}

In my code parent is my current VC. This is probably not the neatest way but it worked at the time.

The callout looks like this:

callout

A couple of booleans calloutVisible and hitTestBuffer make sure the button is only clickable once and only when visible.

Upvotes: 4

gardenofwine
gardenofwine

Reputation: 1362

To try and resolve your issue, I played a bit with Apple's WeatherMap example, and came up with a working solution to your problem.

I added your code to the WeatherAnnotationView.m file (minus the animation code), and on top of that added the code to display the button, and respond to touch events:

-(void)annotationButtonClicked{
    NSLog(@"** button clicked");
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated{
    if(selected)
    {
        //building my custom animation
        annView = [[ UIView alloc ] initWithFrame:(CGRectMake(0, 0, 30, 30)) ];

        annView.frame =CGRectMake(0, 0, 200, 50);

        calloutView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"cloudy.png"]];


        [calloutView setFrame:CGRectMake(-25, 50, 0, 0)];
        [calloutView sizeToFit];
        [annView addSubview:calloutView];


        /* BUTTON CODE */
        UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0,0,20,20)];
        button.backgroundColor = [UIColor orangeColor];

        [button addTarget:self action:@selector(annotationButtonClicked) forControlEvents:UIControlEventTouchUpInside];
        [annView addSubview:button];
        /* END Of BUTTON CODE */

        [self addSubview:annView];

    }
    else
    {
        //Remove your custom view...
        [annView removeFromSuperview];
    }

}

You haven't posted HOW you tried to add a button to the annView, so it is hard for me to understand what was not working with your approach. A common approach for these kind of problems is to remove one piece of code at a time and try to run your app. For starters, try to remove the animation code. Then, try to remove calloutView, and remain only with annView and the button, until you have code that works with a button. After that, add back the pieces you removed earlier (animation, calloutView) one at a time and see which one is causing your button to stop function.

I hope this helps somehow.

Upvotes: 2

Related Questions