11
votes

It should be something really simple, but I have not been successful in getting this to work using blocks. There are questions and answers to this, but all of them I found are solved by the use of CABasicAnimation and not by UIView Block-Based Animation, which is what I am after.

The following code doesn't work (Block-Based), no animation:

CGAffineTransform spin = CGAffineTransformRotate(spiningView.transform, DEGREES_RADIANS(360));

CATransform3D identity = CATransform3DIdentity;
CATransform3D spin2 =  CATransform3DRotate(identity, DEGREES_RADIANS(360), 0.0f, 0.0f, 1.0f);


[UIView animateWithDuration:3.0f
                      delay:0.0f
                    options:UIViewAnimationOptionCurveLinear
                 animations:^
                 {
                     spiningView.transform = spin;
                     //spiningView.layer.transform = spin2;
                     //Have also tried the above, doesn't work either.
                 }
                 completion:^(BOOL finished)
                 {
                     spiningView.transform = spin;
                     //spiningView.layer.transform = spin2;
                 }];

From my understanding, when every time we use Block-Based, no animation would occur, when UIViewAnimation Block "sees" that the begin value is the same as the final value. Fair enough, setting it to move to 360 degree would mean that the object stays where it is. But it has to be a way to use Block-Based Animation to make this animate, because the following CABasicAnimation would work flawlessly:

CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0f];
rotationAnimation.duration = 3.0f;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = 1;
[spiningView.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];  

In Addition, the following Block-Based works, but there is a stop in between animations (first it rotates to 180 degrees, then therefrom to make another 180 degrees to complete the rotation), which is not what I am after:

[UIView animateWithDuration:3.0f
                      delay:0.0f
                    options:UIViewAnimationOptionCurveLinear
                 animations:^
                 {
                     spiningView.transform = CGAffineTransformRotate(spiningView.transform, DEGREES_RADIANS(180));;
                 }
                 completion:^(BOOL finished)
                 {                         
                     [UIView animateWithDuration:3.0f
                                           delay:0.0f
                                         options:UIViewAnimationOptionCurveLinear
                                      animations:^
                                      {
                                         spiningView.transform = CGAffineTransformRotate(spiningView.transform, DEGREES_RADIANS(360));
                                      }
                                      completion:^(BOOL finished)
                                      {

                                      }];

                 }];

I know I could save a lot of time and just surrender myself to using CABasicAnimation and be done with it, but I would like to know why one works and the other doesn't in this case (making a 360 degree rotation). I hope that you can give me a detailed explanation regarding this between the 2 in this case and some code (Block-Based) that can carry out a complete 360 degree rotation.

Thanks in advance.

4
Shouldn't the second transform (in the block-based solution) use 180 degrees, since it's a transformation over the current transform property at that point (which is already 180 degrees)? Also have you tried just using CGAffineTransformMakeRotation(M_PI*2) in a single animations block?matehat
@matehat Within the second animation block in the second block-based solution is in fact 360 degrees, if I used 180 degrees, it would just rotate to 180 degrees (by the first animation block) and stops there. It also seems strange to me that I have to set 360 degrees within the second nested-block instead of 180.Unheilig

4 Answers

27
votes

With UIView animations, Core Animation computes the shortest path between the initial transform and the final transform. The 360° rotation doesn't work because the final transform is the same as the initial transform.

For what it's worth, I've just tried the following code which makes four 90° rotations smoothly with no delay between rotations:

- (void)rotateSpinningView
{
    [UIView animateWithDuration:1.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
        [spiningView setTransform:CGAffineTransformRotate(spiningView.transform, M_PI_2)];
    } completion:^(BOOL finished) {
        if (finished && !CGAffineTransformEqualToTransform(spiningView.transform, CGAffineTransformIdentity)) {
            [self rotateSpinningView];
        }
    }];
}
14
votes

This is something iOS 7's keyframe animations are well suited to. For example, you can split one full rotation of view into three parts:

CGFloat direction = 1.0f;  // -1.0f to rotate other way
view.transform = CGAffineTransformIdentity;
[UIView animateKeyframesWithDuration:1.0 delay:0.0
                               options:UIViewKeyframeAnimationOptionCalculationModePaced | UIViewAnimationOptionCurveEaseInOut
                            animations:^{
                              [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.0 animations:^{
                                view.transform = CGAffineTransformMakeRotation(M_PI * 2.0f / 3.0f * direction);
                              }];
                              [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.0 animations:^{
                                view.transform = CGAffineTransformMakeRotation(M_PI * 4.0f / 3.0f * direction);
                              }];
                              [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.0 animations:^{
                                view.transform = CGAffineTransformIdentity;
                              }];
                            }
                            completion:^(BOOL finished) {}];

Note that when using UIViewKeyframeAnimationOptionCalculationModePaced you can leave all the start times and durations blank.

3
votes

Here is a more complete version of neilco's rotateSpinningView.

It spins clockwise or counter clockwise. It has a completion: parameter and it starts and stops easy.

+ (void)rotateSpinningView:(UIView *)view direction:(BOOL)clockwise completion:(void (^)(BOOL finished))completion
{
    int dir = clockwise ? 1 : -1;
    UIViewAnimationOptions opt = UIViewAnimationOptionCurveLinear;
    if (CGAffineTransformEqualToTransform(view.transform, CGAffineTransformIdentity)) {
        opt = UIViewAnimationOptionCurveEaseIn;
    }
    else if (CGAffineTransformEqualToTransform(CGAffineTransformRotate(view.transform, dir * M_PI_2), CGAffineTransformIdentity)) {
        opt = UIViewAnimationOptionCurveEaseOut;
    }
    [UIView animateWithDuration:0.5f delay:0.0f options:opt animations:^{
        [view setTransform:CGAffineTransformRotate(view.transform, dir * M_PI_2)];
    } completion:^(BOOL finished) {
        if (finished && !CGAffineTransformEqualToTransform(view.transform, CGAffineTransformIdentity)) {
            [self rotateSpinningView:view direction:clockwise completion:completion];
        }
        else if (completion && finished && CGAffineTransformEqualToTransform(view.transform, CGAffineTransformIdentity)) {
            completion(finished);
        }
    }];
}
1
votes

@neilco put a very good solution but it is an infinite rotation.

Based on his code, I modify a bit to implement a one loop rotation.

#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)
- (void)rotateSpinningView:(UIView *)spiningView stop:(BOOL)stop {
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
    double rads = DEGREES_TO_RADIANS(180);
    if (stop) {
        rads = DEGREES_TO_RADIANS(360);
    }
    [spiningView setTransform:CGAffineTransformMakeRotation(rads)];
} completion:^(BOOL finished) {
    if (finished && !stop && !CGAffineTransformEqualToTransform(spiningView.transform, CGAffineTransformIdentity)) {
        [self rotateSpinningView:spiningView stop:YES];
    }
}];

}