I am working on an iOS 7+ app and would like to animated change the content of an UILabel. I do not want do do any graphical animation like fade out old content / fade in new content. Thus all the standard animation features iOS offers like Layer animations or animation blocks cannot be uses (at least I think so).
Assume the UILabel shows some meter values like "200 V" and this text should be changed to "400 V". The text should not just jump from "200 V" to "400 V" but should be counted up using some easing function: "200 V", "220 V", "240 V"... "390 V", "395 V" ... "400 V"
In Android could easily be solved using a ValueAnimator:
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setInterpolation(new EaseOutInterpolator());
animation.setDuration(2500);
animation.setStartDelay(500);
animation.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpate(ValueAnimator animator) {
float currentValue = animator.getAnimatedValue.floatValue();
label1.setText(String.format("%.2", fromValue1 + ((toValue1 - fromValue1) * currentValue)));
label2.setText(String.format("%.2", fromValue2 + ((toValue2 - fromValue2) * currentValue)));
...
}
});
animation.start();
Is there such thing in iOS as well? I found different solution for this but they are all pretty old (2010/11) and all end up implementing this behavior manually using NSTimer and own easing functions.
It is out of question that one can implement this on his own, but this would be quite cumbersome and not very elegant. So: Is there something build in iOS to solve this or are there at least convenient third party implementation available?
Thank you very much!
Since I found no tailored solution for this I created my own: A simple Animator Class which handles the Easing:
// MyValueAnimation.h
typedef void (^MyAnimationBlock)(double animationValue);
@interface MyValueAnimation : NSObject
- (void)startAnimation:(MyAnimationBlock)animationBlock runtime:(NSUInteger)runtime delay:(NSUInteger)delay;
@end
// MyValueAnimation.m
#import "MyValueAnimation.h"
// Number of seconds between each animation step
#define kStepSize 0.05
@interface MyValueAnimation () {
NSTimer *timer;
NSUInteger totalRunTime; // Total duration of the animation (delay not included)
NSUInteger currentRuntime; // Time the animation is already running
MyAnimationBlock animationBlock;
}
@end
@implementation MyValueAnimation
- (void)startAnimation:(MyAnimationBlock)block runtime:(NSUInteger)runtime delay:(NSUInteger)delay {
if (timer != nil)
[timer invalidate];
timer = nil;
totalRunTime = runtime;
animationBlock = block;
currentRuntime = 0;
if (block != nil) {
if (delay > 0) {
// Wait to delay the start. Convert delay from millis to seconds
double delaySeconds = (double)delay / 1000.0;
timer = [NSTimer scheduledTimerWithTimeInterval:delaySeconds target:self selector:@selector(delayTick:) userInfo:nil repeats:false];
} else {
// Run the animation
timer = [NSTimer scheduledTimerWithTimeInterval:kStepSize target:self selector:@selector(animationTick:) userInfo:nil repeats:true];
}
}
}
- (void)delayTick:(NSTimer *)delayTimer {
// End of delay -> run animation
[delayTimer invalidate];
timer = [NSTimer scheduledTimerWithTimeInterval:kStepSize target:self selector:@selector(animationTick:) userInfo:nil repeats:true];
}
- (void)animationTick:(NSTimer *)animationTimer {
NSUInteger step = 1000 * kStepSize; // step size/length in milli seconds
currentRuntime += step;
double progress = MIN((double)currentRuntime / (double)totalRunTime, 1.0);
// Progress is a value between 0 and 1. The easing function maps this
// to the animationValue which is than used inside the animationBlock
// to calculate the current value of the animiation
double animationValue = [self customEaseOut:progress];
if (animationBlock != nil)
animationBlock(animationValue);
if (progress >= 1.0) {
// Animation complete
[timer invalidate];
timer = nil;
}
}
- (double)customEaseOut:(double)t {
// Use any easing function you like to animate your values...
// http://rechneronline.de/function-graphs/
// http://sol.gfxile.net/interpolation/
return (1 - pow(1-t, 2));
}
@end
// =============================================================
// Some code using the animation
- (void)animateValueFrom:(double)fromValue to:(double)toValue {
if (valueAnimation == nil)
valueAnimation = [[MyValueAnimation alloc] init];
MyAnimationBlock animationBlock = ^(double animationValue) {
double currentValue = fromValue + ((toValue - fromValue) * animationValue);
someLabel.text = [NSString stringWithFormat:"%dV", currentValue];
};
[valueAnimation startAnimation:animationBlock runtime:1500 delay:500];
}
Maybe not the prettiest solution but it works :-)
I know a solution but its for fast iteration because final iterations can jump through value (looks not beautifully if there is a slow search), but the decision simple and short, can not the most beautiful from a architecture(but it can be corrected if it is necessary) realisation.
- (void)someAction
{
[self animateValue:0 toValue:1111 withStep:7 andIntervalSpeed:5];
}
-(void)animateValue:(int)value toValue:(int)toValue withStep:(int)step andIntervalSpeed:(int64_t)intervalSpeed
{
self.currentValue = value; // @property (nonatomic) int currentValue;
NSUInteger numberofIteration = (toValue - value)/step;
int64_t interval = 0.0;
for (NSUInteger i = 0; i < numberofIteration; i++) {
dispatch_time_t start = DISPATCH_TIME_NOW;
interval += intervalSpeed;
dispatch_after(dispatch_time(start, interval * USEC_PER_SEC), dispatch_get_main_queue(), ^{
if (((toValue - value)%step != 0) && (i == (numberofIteration-1)))
{
self.currentValue = toValue;
}
else
{
self.currentValue+= step;
}
NSLog(@"%d",self.currentValue);
self.someLabel.text = [NSString stringWithFormat:@"%d",self.currentValue];
});
}
}