Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing a frame in viewDidLayoutSubviews

When a view controller's view is first shown I want to run an animation in which all elements in the view controller slide from outside the bottom of the screen to their natural positions. To achieve this, I do subview.frame.origin.y += self.view.frame.size.height in viewDidLayoutSubviews. I also tried viewWillAppear, but it doesn't work at all. Then I animate them up to their natural positions with subview.frame.origin.y -= self.view.frame.size.height in viewDidAppear.

The problem is that viewDidLayoutSubviews is called several times throughout the view controller's lifespan. As such, when things like showing the keyboard happen all my content gets replaced outside the view again.

Is there a better method for doing this? Do I need to add some sort of flag to check whether the animation has already run?

EDIT: here's the code. Here I'm calling prepareAppearance in viewDidLayoutSubviews, which works, but viewDidLayoutSubviews is called multiple times throughout the controller's life span.

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    [self prepareAppearance];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self animateAppearance];
}


- (NSArray *)animatableViews
{
    return @[self.createAccountButton, self.facebookButton, self.linkedInButton, self.loginButton];
}

- (void)prepareAppearance
{
    NSArray * views = [self animatableViews];
    NSUInteger count = [views count];

    for (NSUInteger it=0 ; it < count ; ++it) {

        UIView * view = [views objectAtIndex:it];
        CGRect frame = view.frame;

        // Move the views outside the screen, to the bottom
        frame.origin.y += self.view.frame.size.height;

        [view setFrame:frame];
    }
}

- (void)animateAppearance
{
    NSArray * views = [self animatableViews];
    NSUInteger count = [views count];

    for (NSUInteger it=0 ; it < count ; ++it) {

        __weak UIView * weakView = [views objectAtIndex:it];
        CGRect referenceFrame = self.view.frame;

        [UIView animateWithDuration:0.4f
                              delay:0.05f * it
                            options:UIViewAnimationOptionCurveEaseOut
                         animations:^{

                             CGRect frame = weakView.frame;
                             frame.origin.y -= referenceFrame.size.height;

                             [weakView setFrame:frame];
                         }
                         completion:^(BOOL finished) {
                         }];
    }
}
like image 603
André Fratelli Avatar asked Nov 24 '14 17:11

André Fratelli


1 Answers

If you need to animate something when view will appear and then not touch subviews later, I would suggest the following:

  • Don't change/touch viewDidLayoutSubviews
  • Add logic to move elements outside the screen (to their initial position before animation) in viewWillAppear
  • Add logic to animate elements into their proper position in viewDidAppear

UPDATE:

If you're using auto-layout (which is very good thing), you can't animate views by changing their frames directly (because auto-layout would ignore that and change them again). What you need to do is to expose outlets to constraints responsible for Y-position (in your case) and change that constraints rather then setting frames.

Also don't forget to include call to [weakView layoutIfNeeded] after you update constraints in the animation method.

like image 104
sha Avatar answered Sep 28 '22 07:09

sha