layoutSubviews during an animation?
Asked Answered
S

4

16

I have a UIView with a bunch of subviews, all positioned using layoutSubviews. When the view is resized, the relative positions all change. I'd like these re-calculations to happen during an animated resize (using +[UIView beginAnimations:] calls). This doesn't seem to be happening. Any ideas?

Strachey answered 12/9, 2010 at 2:39 Comment(0)
E
26

Assumption: You want to have multiple animation steps (i.e. position doesn't change linearly with frame size).

This isn't possible with a single "standard" UIView animations. Why? The frame/bounds is only set once.

Core Animation has three "layer trees":

  • The model tree is where your app thinks things are.
  • The presentation tree is approximately what's being displayed on screen.
  • The render tree is approximately what Core Animation is compositing.

UIView is a (somewhat thin) wrapper around the model layer. During a UIView animation, Core Animation updates the presentation/render tree — the model tree represents the endpoint of animations. The upshot is that your code can (for the most part) treat animations as instantaneous — moving a view from A to B instantly moves it to B; the change just happens to be animated to the user.

There are more complicated things you can do with CALayer/CAAnimation directly, but I haven't investigated this much.

You could chain multiple animations together using -[UIView setAnimationDidStopSelector:]. (You could also try using multiple animations together with setAnimationDelay:, but I'm not sure what happens with multiple animations on the same property; you might have luck with setAnimationBeginsFromCurrentState:.)

If you want really fine-grained control, CADisplayLink (OS 3.1+) is a timer that fires after each screen refresh. A fallback option (for 3.0 support) is to use an NSTimer at 30/60 Hz or so.

Everest answered 12/9, 2010 at 3:48 Comment(1)
Thanks for the detailed look under the covers. While I was peripherally aware of the tree hierarchy, you put together a great synthesis of why Core Animation's architecture basically doesn't allow what I want to do.Strachey
W
14

I know this is an old question, but this code works for me very well (suited for your example of changing frame).

-(void)layoutSubviews{
     [super layoutSubviews];
     // layout your subviews here, or whatever
}

-(void)someMethod{
    double duration=...;
    [UIView animateWithDuration:duration animations:^{
        self.frame = ...;
        [self layoutIfNeeded];
    }];
}

Of course you can call this method from another object. The "trick" is to call layoutIfNeeded (or layoutSubviews directly - same thing, if You change the frame the setNeedsLayout is called).

As tc. nicely explained the "layer trees", You just force the presentation layer to display the final stage of model layer with animation.

The advantage of this method is in possibility to control when the frame/bounds change is animated and when it's instant.

Hope this helps someone:).

Wimsatt answered 5/9, 2012 at 16:33 Comment(4)
This was very helpful!! I never understood this part of Core Animation until I read your answer.Vagal
Might need to call setNeedsLayout before layoutIfNeeded.Birdwell
Well...it depends on what kind of attributes are changed. Nevertheless it should be set automaticaly, or the implementation of change of this attribute should take care of that. For example if you change frame/bounds/center the setNeedsLayout is called. This way You don't need to do layout calculations if there are no changes in layout.Wimsatt
You can get exactly the same effect by setting the UIViewAnimationOptionLayoutSubviews animation option, so you do not need to call layoutIfNeeded.Sample
S
14

Completing @GrizzlyNetch's anwer, you can set the UIViewAnimationOptionLayoutSubviews animation option, so you don't need to call layoutIfNeeded:

-(void)someMethod{
    double duration = ...;
    [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionLayoutSubviews animations:^{
        self.frame = ...;
    } completion:nil];
}
Sample answered 25/9, 2014 at 1:9 Comment(0)
S
5

Posting for completeness. Thanks to tc. for explaining that what I want to do, exactly, is not supported by Core Animation.

I eventually came up with a reasonable solution. Rather then layout my subviews in -layoutSubviews, I do so in -setBounds:. Then, when I wrap a -setBounds: call in a UIView +beginAnimations: block, those positioning calls are also animated, and the end result is everything properly animating to where it should god.

Strachey answered 12/9, 2010 at 13:39 Comment(4)
Oh... If that's the case, then I misread what you were saying. Try view.frame = blah; [view layoutIfNeeded]Everest
no, that will just move them into position, and then animate the bounds. It's what I tried initially.Strachey
I'm trying to achieve something similar, however my setBounds gets only called once (with the final bounds), so my subviews don't get re-aligned during animation. Is there anything I missed?Camembert
Thanks for the -setBounds trick. I use -setFrame: because I also change the origin but the idea is the same. Laying out the views in -layoutSubviews caused many problems in my case because it's called very often destroying my animations.Gillyflower

© 2022 - 2024 — McMap. All rights reserved.