4
votes

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!

1

1 Answers

7
votes

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
}