UIView infinite loop Animation to call a method after every repeat cycle

流过昼夜 提交于 2019-12-08 06:27:56

问题


I am at my wits end trying to come up with a design pattern for this paradigm. I haven't had much luck on this site but at this stage I'll try anything.

I am trying to implement a radar type animation and hence I am rotating a view 360 degrees to represent the radius rotating around the circle. I have placed points around this circle and am able to calculate the angle from the center of the circle using standard trig.

As the radius sweeps around the circle, if it intersects with a point (eg angle of the point equals the angle of the sweeping radius + a tolerance) the point flashes on.

Now, I have approached this paradigm in a number of ways but have not achieved an ideal solution. Here is what I have tried.

FIRST: iOS4 animation blocks. I would rotated the radius 10 degrees at a time with a duration of .1sec and in the completion method, check for intersections with points. The problem here is that if you set the option to repeat, then the completion method doesn't get called. The only time the completion method gets called is when the entire animation terminates, not after every repeat cycle, so this solution doesn't work.

SECOND: Tried explicit animation using CABasicAnimation. Same approach as above, rotating 10 degrees at every .1 seconds and setting the delegate to self and implementing the animationDidFinish method. In the animationDidFinish method, I checked for intersections with point. I set the animation to cumulative and repeatCound to Huge Value. The raduis rotates but once again, the animationDidFinish doesn't get called unless I set the repeatCount to a small number and then it only gets called at the end of the repeatCount, not after every repeat cycle.

THIRD: Using NSTimer and this approach actually works but the animation can be jerky depending on what is going on around the screen. I use a timer tick of .1 sec and initiate a single animation of 10 degrees as well as a intersection check at every tick. The problem with this approach is that it is susceptible to being stalled when starting each animation due to the processor doing other animations in the background. Note that i didn't have this problem with the other two approaches!

FOURTH: I tried using a combination of the two. CABasicAnimation on the radius and NSTimer on the Points. This issue here is that they can get out of sync and happens quite easily if the iDevice goes to sleep and then resumes.

FIFTH: Using the iOS3.0 style animation block and rotating 10 degrees with a duration of .1sec. Setting the delegate and the animationDidStopSelector with the animationDidStop method calling the animation method again as well as checking of intersection with points. This works too but much like the third solution, it is jerky when scrolling and other animations are happening. This is most likely caused by the stop start nature of the animation.

Basically, is there a way to animation a view infinitely but make it call a method after every repeat cycle? Or is there a way to make the third solution work more smoothly?

PLEASE HELP, I HAVE RUN OUT OF DESIGN PATTERNS.


回答1:


Got it working perfectly.

Here is what I did:

1/ Applied a CABasicAnimation rotation object to the layer to rotate 360 degrees every 2 sec. (don't need to make it cumulative that way)

2/ Set up a timer to call the presentation layer from the layer every .1 sec.

3/ Calculate the angle from the 3D transform stored in the presentation layer. Click here for my answer on how to do this.

4/ Check for intersecting angles.

Done :-)

Things to watch out for: The 3D transform is based on angles between 180 and -180 degrees and not 0 to 360 degrees Use atan2 and not atan as the later will only give angles in the first quadrant.




回答2:


OK, I have come a long way since I posted this question.

The accepted design pattern is to actually generate a clock tick (commonly using CADisplayLink) that runs at either 30 frames per second or 60 depending on how smooth and fast you want the animation to be. At every frame callback, you modify the transform on the view/layer and use a transaction to display it.

Some key notes: Build a method to test for intersecting line and point:

-(BOOL)intersectWithAngle:(CGFloat)rad {        
    return (rad <= angle && rad >= angle - angleIncrement);
}

You must use transactions when attempting to modify a property that is animatable to prevent the layer/view from animating the new value itself. (You don't want to animate it twice)

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];

self.transform = rotationTransform;

[CATransaction commit];

No need to keep creating new transforms every frame, this is just wasteful and consumes resources. Best create an instance variable / property called rotationTransform and modify that every frame and re-apply it to the layer/view.

-(void)displayFrame {   
    rotationTransform.a = cos(angle);
    rotationTransform.b = sin(angle);
    rotationTransform.c = -sin(angle);
    rotationTransform.d = cos(angle);

    [CATransaction begin];
    [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];

    self.transform = rotationTransform;

    [CATransaction commit];
}

Next, create a method to actually set up the angle, use an instance variable / property to store the angle and define your angle incrementation (I use 3 degrees), also predefine 2-pi so that you are not always recalculating it.

-(void)processFrame {
    angle += angleIncrement;
    if (angle >= M_2PI) {
        angle -= M_2PI;
    }

    if ([self intersectWithAngle:point.angle]) {
        [point animate];
    } 
}

Finally, build your method that is called by CADisplayLink which sets up the frame and displays it while maintaining frame sync.

-(void)displayLinkDidTick {
    // get the time now
    NSTimeInterval timeNow = [NSDate timeIntervalSinceReferenceDate];

    if (timeOfLastDraw == 0) {
        numberOfTicks = 1;
    } else {
        NSTimeInterval timeSinceLastDraw = timeNow - timeOfLastDraw;
        NSTimeInterval desiredTimeInterval = kFrameDuration;

        numberOfTicks = (NSUInteger)(timeSinceLastDraw / desiredTimeInterval);      
    }

    if (numberOfTicks > 4) {
        numberOfTicks = 4;
        timeOfLastDraw = timeNow;
    } else {
        timeOfLastDraw += numberOfTicks * kFrameDuration;
    }


    while(numberOfTicks--) {

        [self processFrame];
        if (spriteState == SpriteStateIdle)
            break;
        }

    [self displayFrame];

}

This code extract has been heavily modified for this post, in actually fact I do the animation of the scan line and the blipping of the point in the same CADisplayLink instance.




回答3:


I wonder if this would work:

UIView* yourView = ...;
// preparing the callbacks:
void(^)(void) animations = ...; // the animation would rotate 10 degrees
void(^)(BOOL) completion = ^(BOOL finished) {
  // do your radar stuff
  [UIView animateWithDuration:0.1 animations:animations completion:completion];
}

// the first call to start it all
[UIView animateWithDuration:0.1 animations:animations completion:completion];

The idea is to call the animation sequence again each time after finishing your radar activity, assuming the radar check is not a heavy one it should be smooth.



来源:https://stackoverflow.com/questions/3919831/uiview-infinite-loop-animation-to-call-a-method-after-every-repeat-cycle

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!