0

I am trying to create an Animations Manager class, which will handle recursive animations for me. It is working, but is being unreliable. e.g Sometimes, when using it the animations happen "back to back" as it were, with no delay between them. Other times when using it, delays are left between animations (This is happening on other elements located on other view controllers)

Here is my code:

@implementation customAnimationTimer

-(id) initWithTimeInterval:(NSTimeInterval)timeInterval target:(UIView*)target animationType:(NSInteger)animationType
              fromValue:(CGFloat)fromValue toValue:(CGFloat)toValue withDelegate:(id <customAnimationTimerDelegate>)delegate {

    if (self = [super init]) {
        self.delegate = delegate;
        _timeInterval = timeInterval;
        _target = target;
        _animationState = NO;
        _animationType = animationType;
        _fromValue = fromValue;
        _toValue = toValue;

        _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkFire)];
        [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    }
    return self;
}

-(void) displayLinkFire {if (self.delegate) [self.delegate displayLinkUpdate:self];}

@end


@implementation animationManager


static animationManager* sharedHelper = nil;

+ (animationManager*) sharedInstance {
    if (!sharedHelper) sharedHelper = [[animationManager alloc] init];
    return sharedHelper;
}


-(id) init { if (self = [super init]) {[self initArrays];} return self;}

-(void) initArrays {
    _activeTimers = [NSMutableArray arrayWithObjects: nil];
}

-(void) displayLinkUpdate:(customAnimationTimer*)timer {
    if (timer.displayLink.frameInterval != 1) [self animateWithTimer:timer];
    timer.displayLink.frameInterval = (timer.timeInterval/timer.displayLink.duration);
}

-(void) animateWithTimer:(customAnimationTimer*)timer {
    if (!timer.animationState) {

        timer.animationState = true;
        [UIView animateWithDuration:timer.timeInterval animations:^{
           if (timer.animationType == 0) timer.target.alpha = timer.toValue;
        }];

    } else {

        timer.animationState = false;
        [UIView animateWithDuration:timer.timeInterval animations:^{
            if (timer.animationType == 0) timer.target.alpha = timer.fromValue;
        }];

    }
}

-(void) addAnimationToView:(UIView*)view withType:(int)animationType fromValue:(CGFloat)fromValue toValue:(CGFloat)toValue withTime:(CGFloat)time  {
    [_activeTimers addObject: [[customAnimationTimer alloc] initWithTimeInterval:time target:view animationType:animationType fromValue:fromValue toValue:toValue withDelegate:self]];
}

-(void) removeAnimationFromView:(UIView*)view {

    NSInteger index = 900000000;
    for (customAnimationTimer* timer in _activeTimers) {
        if (timer.target == view) {
            index = [_activeTimers indexOfObject:timer];
            [timer.displayLink invalidate];
        }
    }

    if (index != 900000000) [_activeTimers removeObjectAtIndex:index];

}

-(void) removeAnimations {
    for (customAnimationTimer* timer in _activeTimers) [timer.displayLink invalidate];
    [self initArrays];
}


@end
  • Out of all the objects dedicated to synchronizing drawing and timing, you had to pick the one that had nothing to do with drawing. Maintain a pool of callbacks, not timers. NSTimer is not reliable enough to be used for anything close to animation timing. – CodaFi Apr 02 '14 at 13:05
  • Still getting the same results I'm afraid. It seems to be calling everything at the correct time, but the animations aren't happening at the correct times. I have updated the OP with the new code. Any ideas on what is wrong? –  Apr 02 '14 at 16:28
  • Is the CADisplayLink callback being invoked on the main thread? Also, I notice that you are animating with [UIView animateWithDuration]. That will eventually be scheduled on the runLoop. I don't think that you are guaranteed that the animation will run immediately (at least not with frame accurate precision). – blackirishman Apr 02 '14 at 17:12
  • Yes, it's happening on the main thread. It's also not a case of being delayed by a few frames.. I'm talking about half a second here! –  Apr 02 '14 at 17:19

2 Answers2

0

You are probably testing in the simulator. Don't. CADisplayLink is completely unreliable in the simulator. If you are doing to use CADisplayLink, test only on a device.

Another problem with your code is that you should never make assumptions about how often the CADisplayLink will fire. It does not fire regularly. Always look at the timestamp to learn when it actually did fire, and calculate your animation "frame" based on that.

In this example from my own code, I call the first "frame" of the animation 0 and the last "frame" of the whole animation 1. Thus I can calculate the correct "frame" of the animation by comparing the timestamp with the total duration. I maintain state in some instance variables. Here's what happens when the display link fires:

if (self->_timestamp < 0.01) { // pick up and store first timestamp
    self->_timestamp = sender.timestamp;
    self->_frame = 0.0;
} else { // calculate frame
    self->_frame = (sender.timestamp - self->_timestamp) * SCALE;
}
// ... redraw here ...
if (self->_frame > 1.0) {
    [sender invalidate];
    self->_frame = 0.0;
    self->_timestamp = 0.0;
}
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Downloadable code here: https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch04p172CIFilterAnimation/ch17p519CIFilterAnimation/ViewController.m – matt Apr 02 '14 at 19:33
0

Well, having recorded it and played back the two recordings side-to-side and it was indeed my eyes playing tricks on me! One of them was on a blue background and other was on a red background, I guess this had something to do with it! My apologies to people I have confused!