views:

787

answers:

3

I'm trying to create a custom "blinking cursor" in UIKit, I've tried as shown below, having 2 functions that basically keep calling each other until the cursor is hidden. But this leads to a nice infinite recursion... for some reason the functions call each other right away, not each half-second as expected.

I tried returning if the 'finished' parameter is not YES (by uncommenting the 'if (!ok)' line), but that leads to no animation at all...

Any better idea? Did I miss something, is there a much-easier way to make a "blinking cursor"?

- (void)onBlinkIn:(NSString *)animationID finished:(BOOL)ok context:(void *)ctx {
if (cursorView.hidden) return;
//if (!ok) return;
[UIView beginAnimations:nil context:UIGraphicsGetCurrentContext()];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.5f];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(onBlinkOut:finished:context:)];
cursorView.textColor = [UIColor grayColor];
[UIView commitAnimations];
}

- (void)onBlinkOut:(NSString *)animationID finished:(BOOL)ok context:(void *)ctx {
if (cursorView.hidden) return;
[UIView beginAnimations:nil context:UIGraphicsGetCurrentContext()];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.5f];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(onBlinkIn:finished:context:)];
cursorView.textColor = [UIColor clearColor];
[UIView commitAnimations];
}
A: 

Most likely, you are blocking the main event loop and, thus, blocking the animations when you try to only animate on finish.

Instead, set up a timer that fires after 1/2 a second that kicks off the next animation. That timer could be reset on the finish of the previous animation, thus reducing load and making your blink rate a bit more regular (but you'll have to figure out what is most appropriate).

See the NSTimer class's documentation.

Note that any kind of constant animation like this will put a drain on the battery. Not a huge one, by any means, but... still...

bbum
The animation doesn't last very long usually, just the time for someone to enter a number using a custom keypad... There are several numbers on the screen, and a soft blinking cursor is supposed to simply make it obvious which number is being edited (the number is in a custom view, not in a text field). I think the impact on the battery will be negligible (because of the sort time)
Zoran Simic
Yah-- I just mentioned the battery life thing because it is an interesting additional consideration on mobile platforms.
bbum
+2  A: 

On the delegate:

- (void)blinkAnimation:(NSString *)animationId finished:(BOOL)finished target:(UIView *)target
{
    if (shouldContinueBlinking) {
        [UIView beginAnimations:animationId context:target];
        [UIView setAnimationDuration:0.5f];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(blinkAnimation:finished:target:)];
        if ([target alpha] == 1.0f)
            [target setAlpha:0.0f];
        else
            [target setAlpha:1.0f];
        [UIView commitAnimations];
    }
}

And to start the animation:

shouldContinueBlinking = YES;
[self blinkAnimation:@"blinkAnimation" finished:YES target:cursorView];

Also, ensure your class has a shouldContinueBlinking instance variable

rpetrich
Also, setting the alpha instead of the textColor will allow the GPU to do all of the drawing and will improve performance. Similarly, making cursorView a UIView and setting the backgroundColor will use less RAM and CPU than a UILabel (and much less than a UITextView or UITextField)
rpetrich
Excellent! This is much better than what I was going for, and easily reusable
Zoran Simic
+4  A: 

Do it the Core Animation way:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
[animation setFromValue:[NSNumber numberWithFloat:1.0]];
[animation setToValue:[NSNumber numberWithFloat:0.0]];
[animation setDuration:0.5f];
[animation setTimingFunction:[CAMediaTimingFunction
              functionWithName:kCAMediaTimingFunctionLinear]];
[animation setAutoreverses:YES];
[animation setRepeatCount:20000];
[[view layer] addAnimation:animation forKey:@"opacity"];

Where view is the UIView you want to blink. Core Animation makes this very convenient because it will auto reverse the animation for you. Keep in mind that your complete duration is double what you set in the duration field because the value you specify applies to the forward direction. If you want the whole animation to run (forward and then back) in the specified duration, split the duration in half.

Matt Long
Excellent too! I don't have QuartzCore framework in my app, and won't need it, but this is a very good sample I'll keep handy for a future program. More complex (I find) than the "UIView way".
Zoran Simic