Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animate text Content in iOS - Equivalent for Android ValueAnimator

Tags:

ios

animation

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!

like image 474
Andrei Herford Avatar asked Sep 17 '14 08:09

Andrei Herford


People also ask

What is Android ValueAnimator?

ValueAnimator provides a timing engine for running animation which calculates the animated values and set them on the target objects. By ValueAnimator you can animate a view width, height, update its x and y coordinates or even can change its background.

How do you stop value animators?

Use cancel() or stop() , along with a boolean field somewhere. The boolean serves as a flag to tell onAnimationEnd() whether or not it should do its regular work. Default that boolean to true ; flip it to false when you want to block normal animation-end processing.


2 Answers

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];

        });

    }
}
like image 56
Joe Hallenbeck Avatar answered Oct 26 '22 09:10

Joe Hallenbeck


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 :-)

like image 38
Andrei Herford Avatar answered Oct 26 '22 09:10

Andrei Herford