0

I'm trying to animate a some subviews of a subview of an annotation view.

So, to be more clear, the gray pin is my annotation view, which has an invisible subview, and this subview contains those 4 colored circle views.

annotation view
 | invisible view (called typeSelectionView)
    | yellow view (called typeButton)
    | blue view (called typeButton)
    | green view (called typeButton)
    | red view (called typeButton)

enter image description here

First the circles are not visible (transform scale (0, 0) and their center at the head of the gray pin), then, when I select the gray pin, those circles come from the pin head growing and moving to their original position (the one on the picture above).

When I deselect the pin, the opposite animation happens. And when I select and deselect (or the opposite) before the animation is finished, it changes the way it was going (if it was growing it shrinks) reverting the animation in the middle. That is why I need the UIViewAnimationOptionBeginFromCurrentState.

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)annotationView
{
    // in case the typeSelectionView was already being showed by another annotationView
    if (self.typeSelectionView.superview != annotationView) {
        [self.typeSelectionView removeFromSuperview];
        [self.typeSelectionView showTypeButtons:NO animated:NO completion:NULL];
    }

    [annotationView addSubview:self.typeSelectionView];
    [self.typeSelectionView showTypeButtons:YES animated:YES completion:NULL];
}

- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)annotationView
{
    if (self.typeSelectionView.superview) {
        [self.typeSelectionView showTypeButtons:NO
                                       animated:YES
                                     completion:^(BOOL finished) {
                                         if (finished) {
                                             [self.typeSelectionView removeFromSuperview];
                                         }
                                     }];
    }
}

- (void)showTypeButtons:(BOOL)show animated:(BOOL)animated completion:(void (^)(BOOL))completion
{
    void (^block)(void) = ^{
        for (int i = 0; i < self.typeButtons.count; i++) {
            TypeButton *typeButton = self.typeButtons[i];

            if (show) {
                typeButton.center = [self _typeButtonCenterForIndex:i numberOfButtons:self.typeButtons.count];
                typeButton.transform = CGAffineTransformIdentity;
            } else {
                typeButton.center = CGPointMake(viewWidth / 2, viewheight + 5); // pin head center
                typeButton.transform = CGAffineTransformMakeScale(0, 0);
            }
        }
    };

    if (animated) {
        [UIView animateWithDuration:0.5
                              delay:0
                            options:UIViewAnimationOptionBeginFromCurrentState
                         animations:block
                         completion:^(BOOL finished) {
                             if (completion) {
                                 completion(finished);
                             }
                         }];
    } else {
        block();

        if (completion) {
            completion(YES);
        }
    }
}

So far no problem, but when one gray pin is displaying the typeSelectionView (like in the picture above) and then I select another pin, it's like this part:

if (self.typeSelectionView.superview != annotationView) {
    [self.typeSelectionView removeFromSuperview];
    [self.typeSelectionView showTypeButtons:NO animated:NO completion:NULL];
}

did nothing. The 4 circles simple jump from one pin to the other without any animation. What I would expect is that the [self.typeSelectionView showTypeButtons:NO animated:NO completion:NULL]; set those buttons to be "hidden" (scale (0, 0) and center in the head of the pin) without any animation (because the invisible view doesn't have a superview anymore, so they are not even showing), and then when I add the typeSelectionView to the new annotationView with [annotationView addSubview:self.typeSelectionView];, I animate the circles to their original position again with [self.typeSelectionView showTypeButtons:YES animated:YES completion:NULL];.

Btw, doing [typeButton.layer removeAllAnimations] at the beginning of showTypeButtons:animated:completion: does nothing.

How do I solve this?

Thanks in advance.

EDIT: (BONUS if someone can explain that to me... and help me solve it) =)

Suppose the circles are showing like in the picture above, then this code:

NSLog(@"type selected");
[self.typeSelectionView showTypeButtons:NO
                               animated:YES
                             completion:^(BOOL finished) {
                                 NSLog(@"finisheddddd: %d", finished);
                             }];

[self.typeSelectionView showTypeButtons:NO
                               animated:YES
                             completion:^(BOOL finished) {
                                 NSLog(@"finished: %d", finished);
                             }];

generates the following output:

type selected
finished: 1
finisheddddd: 1

So the second animation finishes first and both animations finish with finished == YES. Also the second animation (which finishes first) finishes instantly and not after the end of the animation time.

O.o

Rodrigo Ruiz
  • 4,248
  • 6
  • 43
  • 75

2 Answers2

-1

Check the code I wrote but not tested. Please experiment with it to get your result

- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)annotationView
{
if (self.typeSelectionView.superview)
{
    [self.typeSelectionView showTypeButtons:NO
                                   animated:YES
                                 completion:^(BOOL finished) {
                                     if (finished) {
                                         [self.typeSelectionView removeFromSuperview];
                                     }
                                 }];
}
}

 - (void)showTypeButtons:(BOOL)show animated:(BOOL)animated completion:(void (^)  (BOOL))completion
 {

[UIView animateWithDuration:(animated) ?0.5f : 0.0f
                      delay:(self.animationStarted)? 0.5 : 0.0
                    options:UIViewAnimationOptionBeginFromCurrentState
                 animations:^{
                     self.animationStarted = YES;
                     [self setButtonVisible:show];
                 }
                 completion:^(BOOL finished) {
                     self.animationStarted = NO;
                         if (completion) {
                             completion(finished);
                         }
                     }];
}

-(void)setButtonVisible:(BOOL)visible
{
 [self.typeButtons enumerateObjectsUsingBlock:^(TypeButton typeButton, NSUInteger idx, BOOL *stop) {
        if (visible)
         {
             typeButton.center = [self _typeButtonCenterForIndex:idx numberOfButtons:self.typeButtons.count];
             typeButton.transform = CGAffineTransformIdentity;
         }
         else
         {
             typeButton.center = CGPointMake(viewWidth / 2, viewheight + 5); // pin head center
             typeButton.transform = CGAffineTransformMakeScale(0, 0);
         }

 }];
}
Rajath Shetty K
  • 424
  • 3
  • 9
  • Did you change anything in the didDeselectAnnotationView method? – Rodrigo Ruiz Mar 27 '14 at 16:35
  • I tried your code and the circles are still jumping when a pin is already selected and I select another pin. In addition I noticed some weird delays some times. Thanks for trying to help though =/. – Rodrigo Ruiz Mar 27 '14 at 16:43
-1

Turns out the problem is that when I set my typeButtons non-animated, they are not redraw instantly, so they don't change their current state, so what happens is:

one mapView:didSelectAnnotationView: method call:
 - show the buttons animated

***redraw cycle here***

another mapView:didSelectAnnotationView: method call:
- hide the buttons non-animated
(no redraw cycle here, so buttons are still showing)
- show the buttons animated

So I came up with this shitty solution:

- (void)showTypeButtons:(BOOL)show animated:(BOOL)animated completion:(void (^)(BOOL))completion
{
    void (^block)(void) = ^{
        for (int i = 0; i < self.typeButtons.count; i++) {
            TypeButton *typeButton = self.typeButtons[i];

            if (show) {
                typeButton.center = [self _typeButtonCenterForIndex:i numberOfButtons:self.typeButtons.count];
                typeButton.transform = CGAffineTransformIdentity;
            } else {
                typeButton.center = CGPointMake(viewWidth / 2, viewheight + 5); // pin head center
                typeButton.transform = CGAffineTransformMakeScale(0, 0);
            }
        }
    };

    if (animated) {
        [UIView animateWithDuration:0.5
                              delay:0
                            options:self.typeButtonsChangedWithoutAnimation ? 0 : UIViewAnimationOptionBeginFromCurrentState
                         animations:block
                         completion:^(BOOL finished) {
                             if (completion) {
                                 completion(finished);
                             }
                         }];
        self.typeButtonsChangedWithoutAnimation = NO;
    } else {
        self.typeButtonsChangedWithoutAnimation = YES;

        block();

        if (completion) {
            completion(YES);
        }
    }
}

Without the UIViewAnimationOptionBeginFromCurrentState, the animation starts from its actual state and not from its draw state.

One solution that I found, but did not work was: What is the most robust way to force a UIView to redraw? Even if it did work, I wouldn't want it either...

Btw, I'm not gonna accept this answer, because it sucks. It does not actually solve my question, which is to change the current state, not pass through it. I'm just posting it in case someone sees and gets an idea of how to actually solve my original question.

Community
  • 1
  • 1
Rodrigo Ruiz
  • 4,248
  • 6
  • 43
  • 75