Will Ullrich
Will Ullrich

Reputation: 2228

Animate Change Height and Width at Different Times

I have an image that I am animating in order to make it look as if it is "breathing".

Currently I have the image moving in a decent manner with the following code below: (I am animating a UIView that contains a few UIImageView's, which all move as one)

- (IBAction)animateButton:(id)sender {


    [UIView animateWithDuration:0.64
                          delay:0
                        options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat
                     animations:^{
                         _testView.transform = CGAffineTransformMakeScale(1.08f, 1.02f);

                     } completion:nil];

}

HOWEVER, I can not seem to figure out how to animate stretching the image in the x at a different rate as the y. The point of this is to appear as if the image is actually alive without appearing to cycle through a clear repetitive motion.

I tried by attempting to anchor the center of the UIView to a specific location, then add some number to the width, through an animation of lets say 1.0 seconds. I then tried to simultaneously call another animation that does the same animation only to the height, with a different amount added, for about 1.3 seconds. I could not get these two to perform at the same time though, as one would take precedence over the other.

If someone could lead me in the right direction as to animating a repetitive stretch of the width and height at different rates I would be most appreciative. Thanks!

Upvotes: 1

Views: 148

Answers (1)

danh
danh

Reputation: 62686

Consider that two changes overlapping in time look like this:

        |---- change x ---|
   |---- change y ----|

If the two intervals are arbitrary and overlapping, the can be represented by three animations: one changing one dimension individually, one changing both dimensions together, and another changing one dimension individually.

You can see that there's numerous ways to specify this, but lets try a straight-forward one. To avoid the arithmetic of compounding scales, lets specify a dimension, a pixel change and a duration. For example...

@[ @{@"dimension":@"width", @"delta":@10, @"duration":0.2},
   @{@"dimension":@"both", @"delta":@40, @"duration":0.8}, 
   @{@"dimension":@"width", @"delta":@10, @"duration":0.2} ]

... means a longer change in width straddling a shorter change in height. You can see how this can be a pretty complete language to get done what you want.

We need an animation method that will perform the changes serially. A simple way to do this is to treat the array of actions as a to-do list. The recursive algorithm says: to do a list of things, do the first one, then do the rest....

- (void)animateView:(UIView *)view actions:(NSArray *)actions completion:(void (^)(BOOL))completion {
    if (actions.count == 0) return completion(YES);
    NSDictionary *action = actions[0];
    NSArray *remainingActions = [actions subarrayWithRange:NSMakeRange(1, actions.count-1)];
    [self animateView:view action:action completion:^(BOOL finished) {
        [self animateView:view actions:remainingActions completion:completion];
    }];
}

For the animation, you probably want to use a linear timing curve for the intermediate animations, though I can see you getting more elaborate and change the timing curve at the start and end of the list.

- (void)animateView:(UIView *)view action:(NSDictionary *)action completion:(void (^)(BOOL))completion {
    NSString *dimension = action[@"dimension"];
    CGFloat delta = [action[@"delta"] floatValue];
    NSTimeInterval duration = [action[@"duration"] floatValue];

    CGRect frame = view.frame;

    if ([dimension isEqualToString:@"width"]) {
        frame = CGRectInset(frame, -delta, 0);
    } else if ([dimension isEqualToString:@"height"]) {
        frame = CGRectInset(frame, 0, -delta);
    } else {
        frame = CGRectInset(frame, -delta, -delta);
    }
    [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
        view.frame = frame;
    } completion:completion];
}

If the array of dictionaries is too clumsy to specify (and it is rather general), you could add some convenience methods on top that provide some simpler scheme to the caller and builds the array of more general representation.

Upvotes: 1

Related Questions