0
votes

I have a custom view, CustomView, that can animate itself in response to an animate message:

// in a view controller
[self.customView animate];

Inside -[CustomView animate], the logic is as follows:

- (void)animate {
    for each subview {
        startFrame = subview.frame;
        endFrame = EndFrameFromStartFrame(startFrame);
        subview.animation = AnimationLoopBetween(startFrame, endFrame);
    }
}

* This block is pseudocode.

That is to say, the animate method examines the current frame of each subview and generates an animation ending frame for each subview which is essentially a frame which is a fixed ratio larger than the current frame. Then, it creates one animation per subview that varies the frame of its subview, alternating between the current (or start) frame and the end frame, calculated earlier.

This method works very well in general. It allows CustomView to animate properly regardless of how its superview positions and sizes it.

However, in a few cases, "users" of CustomView (that is, view controllers) call animate very early in their lifetime – so early, in fact, that Auto Layout has not yet made any constraint or layout passes. This means that animate is called when all of CustomView's subview frames are CGRectZero. As you can probably guess, this causes both the starting and ending values of the animations to be calculated incorrectly.

Even after the layout pass is complete, and the views' model layers have the proper frames, the CustomView instance is still not visible, because its subviews are happily animating from one zero-rect to another. Not good.

How should I restructure this animation behavior to allow users of this class to call animate at any time without negative repercussions?


Here are some possible answers I've considered, and why they don't seem satisfactory to me:

  1. In any view controller that uses CustomView, put off the call to animate until viewDidLayoutSubviews.
    This forces an unwieldy restriction on view controllers that use this view.

  2. In CustomView's layoutSubviews method, first call [super layoutSubviews], then restart the animation if it was already running.
    An interesting idea, but CustomView has more than one level of subviews. Thus, even after [super layoutSubviews], many of the views further down the view hierarchy will not yet have valid frames, meaning animate will still misbehave.

  3. In CustomView's layoutSubviews method, first call [super layoutSubviews], then manually layout any required views' frames using [<view> layoutIfNeeded], before finally restarting the animation if it was already running.
    This might actually work, but doesn't seem like a good idea. As much as possible, I'd like to leave layout work to Auto Layout.

1
"As much as possible, I'd like to leave layout work to Auto Layout." But calling super layoutSubviews does perform auto layout. That is what auto layout is. Putting code in layoutSubviews after your call to super is perfectly standard and correct - including, if necessary, changing the constraints and calling super again. - matt

1 Answers

0
votes

Why not just skip the uninteresting case?

- (void)animate {
  if (CGRectEqualToRect(self.frame, CGRectZero) return;
  // ...animate.
}