8
votes

I have an animation block to perform a simple transform based animation, that on completion removes the view in question from its superview.

UIView *msgView = [[UIView alloc] initWithFrame:CGRectMake(160, 120, 160, 100)];

// Do stuff to set up the subviews of msgView.

// Add the msgView to the superview (ViewController) that is going to display it.

CATransform3D transform = CATransform3DMakeScale(2.5, 2.5, 1.0);

[UIView animateWithDuration:5.0 
                 animations:^(void){msgView.layer.transform = transform;}
                 completion:^(BOOL finished){[msgView removeFromSuperview];}];

I then use the code as detailed by Tech Q&A 1673 http://developer.apple.com/library/ios/#qa/qa1673/_index.html to pause the animation.

-(void)pauseLayer:(CALayer*)layer
{
    CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
    layer.speed = 0.0;
    layer.timeOffset = pausedTime;
}

-(void)resumeLayer:(CALayer*)layer
{
    CFTimeInterval pausedTime = [layer timeOffset];
    layer.speed = 1.0;
    layer.timeOffset = 0.0;
    layer.beginTime = 0.0;
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    layer.beginTime = timeSincePause;
}

However, this code does not prevent the completion code from executing. So to prevent the code from executing I change the code for completion to this:

completion:^(BOOL finished){if(finished == TRUE)[msgView removeFromSuperview];};

Whilst checking for finished == TRUE prevents the completion code from being executed whilst the animation block is paused. If the duration time is now exceded before you "unpause" the animation, the completion code will not be executed. ie In this case the msgView remains in the superview.

Is there anyway to pause/unpause both the animation and the timer associated with the completion code (if that's what's going on)?

1
Interesting question. Could be construed as a bug in iOS. To get around this problem, I suggest you get rid of the completion piece of your animation, and instead create your own NSTimer with your current completion being its invocation. This way you can stop your timer inside the pause layer (by setting the fireDate far in the future). The inside resumeLayer you reset fireDate to be (timeSincePause + 5.0)Rikkles
I was hoping to get directly at the CAMediaTiming associated with the completion code, in the same way we do with layer. I think it would be a far better solution.VariableSquid
I wrote a test app under iOS 6.1 and it's behaving correctly, i.e. the transform's timer pauses. What OS are you testing under?Rikkles
iOS5. Please note that the transform is working and pausing/unpausing correctly. It's the behaviour of the completion code during and after the pause/unpause that I'm more concerned with.VariableSquid
That is correct. It is working 100% on my end. When I pause the transform and wait 10 seconds, nothing happens. When I resume it, after it finished it triggers the completion. As expected.Rikkles

1 Answers

5
votes

As I said in the comments above, there doesn't seem to be a problem. The below is tested in iOS 5.1 and 6.1.

Create a storyboard with UIImageView *transView and UIButton *trigger. Here's the class:

TSTViewController.h:

@property (weak, nonatomic) IBOutlet UIImageView *transView;
@property (weak, nonatomic) IBOutlet UIButton *trigger;
@property (nonatomic) NSUInteger bState;

- (IBAction)didPressTrigger:(id)sender;

TSTViewController.m:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.bState = 0;    // 0 is initial state
                        // 1 is transform being animated
                        // 2 is transform paused
                        // 3 is transform ended
}

- (IBAction)didPressTrigger:(id)sender {
    switch (self.bState) {
        case 0:
        {
            CATransform3D transform = CATransform3DMakeScale(2.5, 2.5, 1.0);
            self.bState++;
            [UIView animateWithDuration:5.0
                             animations:^(void){self.transView.layer.transform = transform;}
                             completion:^(BOOL finished){
                                 self.bState = 3;
                                 NSLog(@"Done");
                             }];
            break;
        }
        case 1:
        {
            self.bState++;
            [self pauseLayer:self.transView.layer];
            break;
        }
        case 2:
        {
            self.bState = 1;
            [self resumeLayer:self.transView.layer];
            break;
        }
        case 3:
        {
            [UIView animateWithDuration:0 animations:^(void){self.transView.layer.transform = CATransform3DIdentity;}
                             completion:^(BOOL finished) {
                                 self.bState = 0;
                                 NSLog(@"Reset");
                             }];
            break;
        }
        default:
            break;
    }
}

-(void)pauseLayer:(CALayer*)layer
{
    CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
    layer.speed = 0.0;
    layer.timeOffset = pausedTime;
}

-(void)resumeLayer:(CALayer*)layer
{
    CFTimeInterval pausedTime = [layer timeOffset];
    layer.speed = 1.0;
    layer.timeOffset = 0.0;
    layer.beginTime = 0.0;
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    layer.beginTime = timeSincePause;
}

When you press the trigger button, the animation starts. Press it again, the animation stops. Wait 10 seconds, and press the button yet again. The animation continues and finishes, and it logs "Done".