1

I'm trying to create a custom callout bubble. I have an animation block setting the scale transform of the bubble UIView:

self.view.transform = CGAffineTransformMakeScale(0, 0); // (1)
self.view.transform = CGAffineTransformIdentity; // (2)

I either start the bubble view with scale (0, 0) as in (1) and animate to Identity as in (2) inside the animation block, or the opposite, going from (2) to (1).

If I don't have the [self.view layoutIfNeeded]; in my animation block, going from (1) to (2) works fine as in:

enter image description here

But when going back from (2) to (1) without the [self.view layoutIfNeeded];, the subviews jump to the left before the animation is finished:

enter image description here

Now, if I do add the [self.view layoutIfNeeded]; the subviews animate with the bubble view, but with a sort of delay:

Either going from (1) to (2):

enter image description here

Or from (2) to (1):

enter image description here

I have already tried replacing all the top and leading subview's constraints with center constraints like in How do I adjust the anchor point of a CALayer, when Auto Layout is being used? and have also tried the layer transform solution (but this one breaks the layout constraints saying it can't satisfy all constraints).

Any ideas on how to solve my animation problem?

Thanks in advance.

UPDATE: I'm updating the question with the actual method that I call on

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)annotationView

and

- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)annotationView

to present and dismiss my custom callout bubble (forget the Expanded state for now).

- (void)setCalloutState:(CalloutState)calloutState
               animated:(BOOL)animated
         annotationView:(MKAnnotationView *)annotationView
             completion:(void (^)(BOOL finished))completion
{
    if (self.view.superview == annotationView || !annotationView) {

    } else {
//        [self.view.layer removeAllAnimations];
        [self.view removeFromSuperview];

        self.view.transform = CGAffineTransformIdentity;
        self.view.bounds = CGRectMake(0, 0, normalViewWidth, normalViewHeight);
        self.view.transform = CGAffineTransformMakeScale(0, 0);
        self.view.center = CGPointMake(annotationView.bounds.size.width / 2, 0);

        [annotationView addSubview:self.view];
    }

    void (^animationBlock)(void) = ^{
        self.view.transform = CGAffineTransformIdentity;

        switch (calloutState) {
            case CalloutStateHidden:
                self.view.bounds = CGRectMake(0, 0, normalViewWidth, normalViewHeight);
                self.view.transform = CGAffineTransformMakeScale(0, 0);
                break;
            case CalloutStateNormal:
                self.view.bounds = CGRectMake(0, 0, normalViewWidth, normalViewHeight);
                break;
            case CalloutStateExpanded:
                self.view.bounds = CGRectMake(0, 0, expandedViewWidth, expandedViewHeight);
                break;
            default:
                break;
        }

        self.view.center = CGPointMake(annotationView.bounds.size.width / 2, 0);

        [self.view layoutIfNeeded];
    };

    if (animated) {
        // TODO: figure out why the first animateWithDuration is needed in this nested thing
        [UIView animateWithDuration:0
                              delay:0
                            options:UIViewAnimationOptionBeginFromCurrentState
                         animations:NULL
                         completion:^(BOOL finished) {

                             [UIView animateWithDuration:1.3
                                                   delay:0
                                                 options:UIViewAnimationOptionBeginFromCurrentState
                                              animations:animationBlock
                                              completion:^(BOOL finished) {
                                                  if (finished) {
                                                      self.calloutState = calloutState;

                                                      // TODO: figure out how to end UIView animation instantly so we don't need the second condition at the if
                                                      // having a concurrency problem here
                                                      if (calloutState == CalloutStateHidden && self.view.superview == annotationView) {
                                                          [self.view removeFromSuperview];
                                                      }

                                                      self.editingEnabled = calloutState == CalloutStateExpanded;
                                                  }

                                                  if (completion) {
                                                      completion(finished);
                                                  }
                                              }];

                         }];
        // ---------------------------------------------------------------------------------
    } else {
        animationBlock();

        self.calloutState = calloutState;
        if (calloutState == CalloutStateHidden) {
            [self.view removeFromSuperview];
        }

        self.editingEnabled = calloutState == CalloutStateExpanded;

        if (completion) {
            completion(YES);
        }
    }
}
Community
  • 1
  • 1
Rodrigo Ruiz
  • 4,248
  • 6
  • 43
  • 75
  • 1
    pass this option for animation UIViewAnimationOptionLayoutSubviews.. it should work without calling layoutIfNeeded method... – Rajath Shetty K Mar 24 '14 at 05:19
  • I tried and if I replace the [self.view layoutIfNeeded] call with this option, it's like I don't have the call at all. It seems like this option does nothing. Although even if it did, I would still have the problem I'm having when I do call [self.view layoutIfNeeded]. – Rodrigo Ruiz Mar 24 '14 at 05:22
  • Use this option UIViewAnimationOptionAllowAnimatedContent.. it may work.. – Rajath Shetty K Mar 24 '14 at 05:38
  • Can you check if starting the scale from (0.001,0.001) instead of (0,0) solves the layout issue ? – CodenameLambda1 Mar 24 '14 at 05:39
  • The UIViewAnimationOptionAllowAnimatedContent does nothing, and the (0.001,0.001) scale causes a weird animation to happen O.o – Rodrigo Ruiz Mar 24 '14 at 05:56

1 Answers1

1

This might help. As per Apple documentation -layoutIfNeeded forces layout early

- (void)setNeedsLayout;
- (void)layoutIfNeeded;

So, you can try setNeedsLayout. This should satisfy the first case you mentioned.

shim
  • 9,289
  • 12
  • 69
  • 108
Gautam Jain
  • 2,913
  • 30
  • 25