3
votes

See How to manage CALayer animations throughout a hierarchy for a follow-up question that is concerned just with how to synchronize parent-child layer animations.

This is a design question concerning running dependent animations at different levels of the view hierarchy in response to animations up the hierarchy.

I have a container controller that has any number of child controllers. The parent controller organizes this content on the screen and at various points needs to change the size and positions of its children views.

I'm trying to animate each of these child views to transition its size/shape/position from their original starting point to the destination. A basic first pass with some basic animations starts to get the job done.

Things get more complicated though by the fact that some of the views being resized should also be performing animations on the contents of the view. Imagine a child view with centered content. As the child is shrunk or expanded, the centered content should be animated alongside the outer animation to compensate for the bounds changes so that the content stays centered.

To further complicate matters, I also have a mask layer for each child view that needs to be animated alongside the child’s animation. I also have some gestures that require NO animations to be used for transitions - so I need a way to sometimes animate the whole view/layer tree, and sometimes not.

So all of this gives me an architecture where I have something like the following

ContainerViewController.view
 -> auxiliary and decorative views
 -> WrapperView (multiple)
    ----> mask layer
    -> Child controller view
       -> subviews & layers

Now my question is really one of maintainability. I can animate each of these parts using explicit or implicit animations. What I need to figure out is what’s the best way to make sure that all of the animations being done are done using the same duration and timing function. At present, I trigger a lot of these off of property changes. In some cases the property changes come from layoutSubviews (triggered from setNeedsLayout).

So, what’s the best strategy for setting up these animations, especially the explicit ones. Is the best that I can do just picking up values from CATransaction? My fear is that not every property needs to be animated in every case (like in the auxiliary views) - I already am flipping setDisableActions on/off to force/deny some property animations.

Should CATransaction be used to trigger the setup of explicit view animations? How do I bind the parameters specified for a UIView animation to the parameters that will be used for the underlying layers? The following code seems to get the job done, but seems really ugly.

-(void) animateForReason:(enum AnimationReason) animationReason
              animations:(void(^)()) animationBlock completion:(void(^)(BOOL)) completionBlock {
    const auto animationDuration = 3.0; // make this long to be noticeable!
    [UIView animateWithDuration:animationDuration delay:0 options:UIViewAnimationOptionLayoutSubviews
                     animations:^{
                         [CATransaction begin];
                         [CATransaction setAnimationDuration:animationDuration];
                         animationBlock();
                         [CATransaction commit];
                     }completion:completionBlock];
}

I think that UIViewControllerTransitionCoordinator is out because I need to do all of these animations in response to user actions, not just external things, like rotations or frame changes.

1

1 Answers

1
votes

There are a few options to consider:

  1. For UIView transitions, you can pass the UIViewAnimationOptionLayoutSubviews option. This will animate the changes between subviews before and after calling layoutSubviews on the view whose frame you just changed.
  2. Instead of using layoutSubviews at all, you could override setBounds: or setFrame: on your UIView and CALayer subclasses. This way, if they're called within an animation block, the subviews will animate together with the superview. If they're not called within an animation block, they'll update instantly.

My fear is that not every property needs to be animated in every case (like in the auxiliary views) - I already am flipping setDisableActions on/off to force/deny some property animations.

Generally, if you want it animated, put it in an animation block, and if you don't, don't. Obviously, it can get more complex than this, which is why Apple sometimes has a setter with an animated argument (like setSelected:animated:).

If you have sometimes on, sometimes off properties, follow this pattern yourself. One possible implementation:

- (void) setNumberOfWidgets:(int)widgetCount animated:(BOOL)animated {
    BOOL oldAnimationValue = [UIView areAnimationsEnabled];

    if (!animated) {
        [UIView setAnimationsEnabled:NO];
    }

    // a related property which may or may not cause an animation
    self.someOtherProperty = someValue;

    if (!animated) {
        [UIView setAnimationsEnabled:oldAnimationValue];
    }
}

My question is really one of maintainability.

Yes, it sounds like you're thinking about some kind of giant object that manages all the animation possibilities for you. Resist this temptation. Let each view be responsible for animating its own subviews, and tell a view (don't let it ask) whether or not these changes should be animated.

I think that UIViewControllerTransitionCoordinator is out because I need to do all of these animations in response to user actions, not just external things, like rotations or frame changes.

Yes, that's correct. Don't use UIViewControllerTransitionCoordinator for this.