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:
But when going back from (2) to (1) without the [self.view layoutIfNeeded];
, the subviews jump to the left before the animation is finished:
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):
Or from (2) to (1):
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);
}
}
}