views:

212

answers:

1

I have a series of (say) boxes on the screen in a row, all subviews of my main view. Each is a UIView. I want to shift them all left and have a new view also enter the screen from the right in lockstep. Here's what I'm doing:

// First add a dummy view offscreen
UIView * stagingView = /* make this view, which sets up its width/height */
CGRect frame = [stagingView frame];
frame.origin.x = /* just off the right side of the screen */;
[stagingView setFrame:frame];
[self addSubview:stagingView];

And then I set up animations in one block for all of my subviews (which includes the one I just added):

[UIView beginAnimations:@"shiftLeft" context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(_animationDidStop:context:)];        
[UIView setAnimationDuration:0.3];
for (UIView * view in [self subviews]) {
    CGRect frame = [view frame];
    frame.origin.x -= (frame.size.width + viewPadding);
    [view setFrame:frame];
}
[UIView commitAnimations];        

Here's what I expect: The (three) views already on screen get shifted left and the newly staged view marches in from the right at the same time.

Here's what happens: The newly staged view animates in exactly as expected, and the views already on the screen do not appear to move at all! (Or possibly they jump without animation to their end locations).

And! If I comment out the whole business of creating the new subview offscreen... the ones onscreen do animate correctly!

Huh?

UPDATE: After some more poking around, I found something amazing: I can fix the problem by making my custom subclass of layoutSubviews return without doing any work if it's called between the time the animation is set up, and the time it completes. That method needs to be doing some work to tweak the position of these subviews during orientation changes. But I don't really understand the interplay of the animations with this method-- I would think it would get called eventually perhaps at the end, which would make sense and which should essentially be a no-op after the animation. Can anyone explain the interaction to me? It seems like I'm close to enlightenment but not quite there yet. Thanks!

+1  A: 

As with the commenters, I am curious why you would call

[self stagingView];

I imagine it is because you are trying to trigger drawRect:. I also wonder why you haven't added it as a subview in the example code, though I imagine you have done it somewhere.

I have 2 things that might help:

1.Don't animate the "boxes" individually. Create a container view for them, then just animate the container view. Simple!

OR

2.Don't rely on [view subviews] to work properly. Keep your own array of the boxes you want to move (an ivar) and iterate over that instead.

Corey Floyd
Er, yeah, typo in the condensation for SO post-- that call is, in fact, a call to `addSubview:`-- fixed! Thanks for the suggestions. Will try one or both of those out; although I don't know why I shouldn't be able to trust `[view subviews]`-- that should be the source of truth here.
quixoto
it should, just prevents user error. For instance, if you decide to add subviews for a different purpose later.
Corey Floyd
@Corey: Thanks for the attention here. I noted an update/fix in the body of the question-- do you know why that fix works? Thanks.
quixoto
Hmm, well your views are off screen which UIKit likes to optimize by not really drawing them correctly, or at all, sometimes (It isn't really documented behavior). But calling methods like layoutSubviews or setNeedsDisplay sometimes will help get offscreen views rendered properly.
Corey Floyd
Giving you the correct answer since you took the time to offer your thoughts. Thanks!
quixoto