Search code examples
iphoneobjective-cxcodeioscore-animation

Changing a ongoing animation


Basically, what i'm trying to do is animate a cloud, and then change it's speed and/or direction mid-animation if the wind changes. If it matters, i am controlling the whole thing from a UIViewController, and the cloud exists of a UIView with a CALayer on top of that, which is the cloud itself. I have also tried it with a UIImageView on top.

For the TL:DR types, in short what i'm trying to do is either get the position of a animating view, or stop a animating view using block animations.

And here's the full story. My problem is to get its position during the animation. I am using block animations. Because i only know the speed with which it should move, i need to calculate the time myself by using the distance left. I have tried the following code, and several variations of it:

[[Cloud CloudImage]convertPoint:CGPointMake([[[Cloud CloudImage] presentationLayer] position].x, 0) toLayer:self.layer].x

Where Cloud is the UIView and CloudImage is the CALayer. This was the most complex variation i tried of this, i tried various simpler ones (with directly asking the Cloud, for example, or with UIView instead of CALayer). However, all it returns is its final value. I read something about this method being broken from 3.2, but being fixed in 4.2; However, it was not fixed when i changed the deployment target to iOS 4.3 instead of 4.0. I am using the 4.3 base sdk.

A few other variations i considered were stopping the animation alltogether for a moment, then getting the position and starting the new animation right away. However, i will need to know a way to stop a block-based animation in its tracks, and i have only found snippets for the older animation system (commitanimations).

The last one i considered was writing my own kind of animation system; The cloud would have a repeating NSTimer at 0.08 seconds or so, and create a 0.08 second core animation each time it fired, for which it uses the speed given to the cloud as a property. However, i fear that any variation of this will have much lower performance, while i need it to be as lightweight as possible, for i have up to 20 of these clouds simultaneously (and sometimes with rain too).

Thanks in advance!


Solution

  • I definitely would roll my own system in this case, and the performance loss versus using built-in animation code can be minimized using CADisplayLink rather than NSTimer (CADisplayLink is directly tied to the on-screen updates timer).

    The fact that you have 20 clouds simultaneously doesn't really change things too much, as I presume your intention is to animate those 20 clouds separately using the built in animations already.

    If you're unsure of how big of a performance hit it will have on things in the end, you could try simply adding a bunch of clouds (50 or so) using the built in animation and see how sluggish they move, then switching it out to built-in animation code and comparing.

    Edit: this discussion on Stack Overflow goes into detail on how to do what you're asking, in case you go that route: Cancel a UIView animation?

    Example:

    // startAnimating called once to initiate animation loop. might be stopped e.g. 
    // if game is paused or such
    - (void)startAnimating
    {
        // displayLink = the CADisplayLink for the current animation, if any.
        if (displayLink) {
            [displayLink invalidate]; 
            [displayLink release];
        }
    
        displayLink = [[CADisplayLink displayLinkWithTarget:self
                                                   selector:@selector(animationTick:)] 
                       retain];
    
        [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    }
    
    // tick method is called at every interval; we want to update things based on 
    // a delta time (duration), so that if things get bogged down and the updates 
    // come less often, we don't go into slow motion
    - (void)tick:(CADisplayLink *)sender
    {
        CFTimeInterval duration = sender.duration;
        // here, we update the position for all the UIView objects. example:
        CGRect cloudFrame;
        for (UIView *cloud in clouds) {
            cloudFrame = cloud.frame;
            cloudFrame.origin.x += windForceX * duration;
            cloudFrame.origin.y += windForceY * duration;
            cloud.frame = cloudFrame;
        }
        // here we might update the windForceX and Y values or this might happen somewhere
        // else
    }