Search code examples
iosmultithreadingtimerbackground-process

Preferred way to run custom animations


I need to run a complex custom animation in an iOS app. For this, I wrote a function that needs to be called repeatedly and uses the current time-stamp to calculate positions, colors, shadows, etc. of UIView elements on the screen.

There seem to be a whole bunch of different approaches I could use to have this function called:

I tried calling my animation-function from a separate thread first, but while the thread does run, I don't see any screen updates until I trigger a refresh manually with a device rotation, so I must be missing some step where I call the update functions from inside the GUI Thread instead of my own or invalidating the View or something... But I don't even know if this is the best approach...

What is the preferred way to keep calling a function (for an animation, for example) as quickly as possible (or with a small delay of 10ms or so) without blocking the GUI and in such a way that if this function, for example, changes the background color or position of a view, the screen gets updated?

If possible, I would like to use a method that is as backward-compatible as possible, so preferably something that doesn't use any features introduced in iOS 8.1 (exaggeration)... :)

Aside:

Sorry for not posting a code example. I'm using RoboVM and don't want to "scare off" any answers from true XCode developers. Also, this is more of a general conceptual question rather than a specific bug-fix.


Solution

  • I've found the best performance from CADisplayLink.

    displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkTick)];
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    
    - (void)displayLinkTick {
      // Update your animation.
    }
    

    Don't forget to teardown when you're destroying this view or else you'll have your displayLinkTick called until your application exits:

    [displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    

    Alternatively, if you're using (or convert to) CALayer, your subclass would return YES from needsDisplayForKey: on your animating key. Then, in your CALayer subclass' display method, you'd apply the changes that your self.presentationLayer has for your animation.

    @property (assign) CGFloat myAnimatingProperty;
    
    @implementation MyAnimatingLayer : CALayer
    + (BOOL)needsDisplayForKey:(NSString *)key {
      if ([key isEqualToString:@"myAnimatingProperty"]) {
        return YES;
      }
      return [super needsDisplayForKey:key];
    }
    
    - (void)display {
      if ([self.animationKeys containsObject:@"myAnimatingProperty"]) {
        CGFloat currentValue = self.presentationLayer.myAnimatingProperty;
        // Update.
      }
    }
    @end
    

    This second way will allow you to link in with the built-in easing functions really easily.