Preferred way to run custom animations

烂漫一生 提交于 2019-12-12 04:49:40

问题


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:

  • Multi-Threading
  • Timers
  • Dispatch Queues
  • God knows what else... :)

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.


回答1:


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.




回答2:


In case someone else is looking for a solution for RoboVM, here you go:

import org.robovm.apple.coreanimation.CADisplayLink;
import org.robovm.apple.foundation.NSObject;
import org.robovm.apple.foundation.NSRunLoop;
import org.robovm.apple.foundation.NSString;
import org.robovm.objc.Selector;
import org.robovm.objc.annotation.BindSelector;
import org.robovm.rt.bro.annotation.Callback;

// Requires iOS 3.1
public abstract class DisplayRefreshTimer extends NSObject implements Runnable {

    private static final Selector REFRESH = Selector.register("displayRefresh:");
    private static final NSString RUNMODE = new NSString("kCFRunLoopDefaultMode");

    public DisplayRefreshTimer() {
        CADisplayLink displayLink = CADisplayLink.create(this, REFRESH);
        displayLink.addStrongRef(this);                // Don't garbage collect "this"
        displayLink.addToRunLoop(NSRunLoop.getCurrent(), RUNMODE);    // Start calling
    }

    @Callback @BindSelector("displayRefresh:")
    private static void displayRefresh(DisplayRefreshTimer __self__) {
        if (__self__!=null) __self__.run();
    }
}

Just sub-class this, override run() and instantiate:

new DisplayRefreshTimer() {
    @Override
    public void run() {
        // Do your magic here...
    }
};

Done...

Note: The constant String "kCFRunLoopDefaultMode" might change and should instead be read from the NSDefaultRunLoopMode constant that should be provided by NSRunLoop. For some reason RoboVM removed access to this constant (some details here). While I would think it's unlikely, Apple might decide to change this constant in the future, in which case apps based in this code will break.



来源:https://stackoverflow.com/questions/26719836/preferred-way-to-run-custom-animations

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