Animating a pulsing UILabel? - iphone

I am trying to animate the color the the text on a UILabel to pulse from: [Black] to [White] to [Black] and repeat.
- (void)timerFlash:(NSTimer *)timer {
[[self navTitle] setTextColor:[[UIColor whiteColor] colorWithAlphaComponent:0.0]];
[UIView animateWithDuration:1
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{[[self navTitle] setTextColor:[[UIColor whiteColor] colorWithAlphaComponent:1.0]];}
completion:nil];
}
.
[self setFadeTimer:[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerFlash:) userInfo:nil repeats:YES]];
Firstly I am not sure of my method, my plan (as you can see above) was to set up a animation block and call it using a repeating NSTimer until canceled.
My second problem (as you can see above) is that I am animating from black (alpha 0) to white (alpha 1) but I don't know how to animate back to black again so the animation loops seamlessly
Essentially what I want is the text color to pulse on a UILabel until the user presses a button to continue.
EDIT_001:
I was getting into trouble because you can't animate [UILabel setColor:] you can however animated [UILabel setAlpha:] so I am going to give that a go.
EDIT_002:
- (void)timerFlash:(NSTimer *)timer {
[[self navTitle] setAlpha:0.5];
[UIView animateWithDuration:2
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{[[self navTitle] setAlpha:0.9];}
completion:nil];
}
This works (BTW: I do want it to stop which is why I hooked it up to a NSTimer so I can cancel that) the only thing is that this animates from midGray to nearWhite and then pops back. Does anyone know how I would animate back from nearWhite to midGray so I get a nice smooth cycle?
EDIT_003: (Solution)
The code suggested by Dave DeLong (see below) does indeed work when modified to use the CALayer opacity style attribute:
UILabel *navTitle;
#property(nonatomic, retain) UILabel *navTitle;
.
// ADD ANIMATION
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"opacity"];
[anim setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[anim setFromValue:[NSNumber numberWithFloat:0.5]];
[anim setToValue:[NSNumber numberWithFloat:1.0]];
[anim setAutoreverses:YES];
[anim setDuration:0.5];
[[[self navTitle] layer] addAnimation:anim forKey:#"flash"];
.
// REMOVE ANIMATION
[[[self navTitle] layer] removeAnimationForKey:#"flash"];

You could probably do this with a CAAnimation. I just pushed a little demo project I did a while ago onto github. It shows the olympic logo, and makes the rings fly around to a random location on the screen, then animate back to their original position. You could probably do something very similar, but obviously not using the position property.
Here's the basic code to make one of the rings fly around:
CABasicAnimation * a = [CABasicAnimation animationWithKeyPath:#"position"];
[a setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[a setFromValue:[NSValue valueWithCGPoint:[layer position]]];
[a setToValue:[NSValue valueWithCGPoint:[self randomPoint]]];
[a setAutoreverses:YES];
[a setDuration:1.0];
[layer addAnimation:a forKey:nil];
Your fromValue and toValue values would be CGColorRefs, and your animation keypath would be different, but the trick here is the setAutoreverses:YES. This will animate from the fromValue to the toValue, and then back to the fromValue. It's pretty darn handy. :)
https://github.com/davedelong/Demos/blob/master/OlympicRings
If that doesn't work (and it might not), then I might just layer two UILabels on top of each other and animate their alpha values from 0.0 to 1.0 (or vice versa) at the same time.

If you just want it to happen indefinitely, you can use Dave DeLong's answer, or this:
[UIView animateWithDuration:1
delay:0
options:UIViewAnimationOptionAllowUserInteraction |
UIViewAnimationOptionAutoreverse |
UIViewAnimationOptionRepeat
animations:^{[[self navTitle] setTextColor:[UIColor whiteColor]];}
completion:nil];
The advantage of using CAAnimation, of course, is that you can remove the animation later; with this, to stop it you'd have to dig down into the CALayer and find the animation to stop it.
You suggested, however, that UILabel.textColor might not be animatable. That's a possibility. If so, you can use this same technique to transition between two UILabels of the different colors and just fade their alpha values.

(same as here Cancel block in UIView animateWithDuration)
I just repeat the animation and kill it after some time with a dispatch block:
- (void)animate // Blink a view three times
{
// We need to stop the animation after a while (0.9 sec)
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.9 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
{
[view.layer removeAllAnimations];
view.alpha = 0.0; // Animation clean-up
});
[UIView animateWithDuration:0.15 // 0.15*6=0.9: It will animate six times (three in reverse)
delay:0.0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat
animations:^{
view.alpha = 1.0; // Animation
}
completion:NULL];
}

Related

How can I rotate a UIButton continuously and be able to stop it?

I want to rotate a UIButton continuously as a means of indicating that an app is recording something. Is this a good idea. I also want to be able to stop this continuous rotation.
How would I use animation to do this?
Use the UIView animation method:
animateWithDuration:delay:options:animations:completion:
and use the option UIViewAnimationOptionRepeat
For example for a UIButton * outlet named button:
- (void)viewDidLoad
{
[super viewDidLoad];
[UIView animateWithDuration:2.0 delay:0.0 options:UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveLinear animations:^{
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI);
self.button.transform = transform;
} completion:NULL];
}
Since it's going round and round, I just rotate it by pi radians rather than 2pi. you could use any angle you want.
Edit
To stop the animation, just create another animation of a short duration beginning from the current state, e.g.
- (void)stopRotating {
[UIView animateWithDuration:0.1 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveLinear animations:^{
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI * 0.05);
self.button.transform = transform;
} completion:NULL];
}
This provides a very short animation which overrides the current animation. Note that the angle of the transform is multiplied by 0.05 which is the ratio of 0.1/2.0 which means that the rotational speed for this little segment is the same as the continuous rotational speed.
Try below code that will help you because I run that code successfully.
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:#"Show View" forState:UIControlStateNormal];
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[self.view addSubview:button];
CABasicAnimation *halfTurn;
halfTurn = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
halfTurn.fromValue = [NSNumber numberWithFloat:0];
halfTurn.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
halfTurn.duration = 0.5;
halfTurn.repeatCount = HUGE_VALF;
[[button layer] addAnimation:halfTurn forKey:#"180"];
I had the same problem myself. In the end I made it like that:
#pragma mark Start/StopSpining
-(void)startSpin{
isSpining = YES;
[self Spin];
}
-(void)stopSpin{
isSpining = NO;
}
-(void)spin{
[UIView animateWithDuration:1.0 delay:0 options: UIViewAnimationOptionCurveLinear animations:^{
self.spinCircle.transform = CGAffineTransformRotate(self.spinCircle.transform, M_PI/2);
} completion:^(BOOL finished) {
if (isSpining)
[self spin];
}];
}
Try below extension for swift 3.
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 3) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(M_PI * 2)
rotateAnimation.isRemovedOnCompletion = false
rotateAnimation.duration = duration
rotateAnimation.repeatCount=Float.infinity
self.layer.add(rotateAnimation, forKey: nil)
}
}
For start rotation.
MyButton.rotate360Degrees()
And for Stop.
MyButton.layer.removeAllAnimations()
I suggest you don't rotate the UIButton, just the image you put on the button. Who knows what complications or overhead rotating a rectangular object that can detect touches introduces - what happened if your user somehow taps in the corner of the button and then releases the tap as the button has rotated so that the touch is now outside? Does that register a touch up inside or not?
Anyway, if you want continuous rotation use a block method:
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
seems to fit the bill. The complete rotation (2pi) will occur every duration and you cause it to repeat until you stop it using UIViewAnimationOptionRepeat. You can check with completion to see if you should begin another cycle when one animation cycle has ended and you can stop the animation at any time with
[myView.layer removeAllAnimations];
(If you simply want to run it until you stop it then there are other variants of this method without a completion block)
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.3];
uiButtonObject.transform = CGAffineTransformMakeRotation(M_PI);
[UIView commitAnimations];
M_PI is the radian value of the angle you want to rotate. Run this in a loop and in a different thread. You might also want to run the above using performSelectorInMainThread method as UI changes are not permitted in the UIThread.

Combining animations on UIView and subiview's CALayer?

I'm animating a UIView transition like so:
UIView *areaView = areaController.view;
[areaController viewWillAppear:YES];
UIView *subView = self.customView.subView;
UIButton *button = customView.button; // button is a subview of customView.subView
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:subView cache:YES];
[button removeFromSuperview];
[subView addSubview:areaView];
[areaController viewDidAppear:YES];
[UIView commitAnimations];
[self.areaController start]; // areaController starts the animation of the CALayer, see below
This flips the new view, areaView from "behind" the button just fine, but in areaView there's another subview which is animating it's CALayer:
CATransform3D rotate = CATransform3DMakeRotation(angle, 1, 0, 0);
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.toValue = [NSValue valueWithCATransform3D:rotate];
animation.duration = .08;
animation.delegate = self;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
[layer addAnimation:animation forKey:#"transform"];
My question is:
How do I make both these animations animate in parallell?
Currently, the animation on the CALayer starts when calling [self.areaController start];, but the result is not visible until the transition on the superview is finished. Should I somehow use an animation group with the UIView's layer?
I think you can't do these at the same time. What you can do is do the first animation also using the CATransform3D, then they will occur at the same time.
This is an old post though so maybe you've already solved the issue.

Animating UILabel opacity using CALayer?

I am animating the alpha of a UILabel so that it flashes white when a button is pressed. The label is pure white [r255,g255,b255,a1] to achieve the flash I am animating the CALayer opacity from 0.5 to 1.0 and then back to 0.5. The code to do this: (thanks to Dave DeLong for the help) is:
UILabel *navTitle;
#property(nonatomic, retain) UILabel *navTitle;
...
...
#synthesize navTitle;
.
// ADD ANIMATION OBJECT
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"opacity"];
[anim setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[anim setFromValue:[NSNumber numberWithFloat:0.5]];
[anim setToValue:[NSNumber numberWithFloat:1.0]];
[anim setAutoreverses:YES];
[anim setDuration:0.5];
[[[self navTitle] layer] addAnimation:anim forKey:#"flash"];
[[self navTitle] setTag:1138];
As the flash gets called multiple times (i.e. each time the button is pressed) I am calling removeAnimationForKey before the next flash, my question, is this correct (i.e. the bit where I remove the animation from the layer). If I did not remove the layer am I right in assuming that they will just build up as I add more and more?
// REMOVE ANIMATION OBJECT
if([[self navTitle] tag] == 1138) {
[[[self navTitle] layer] removeAnimationForKey:#"flash"];
[[self navTitle] setTag:0];
}
NB: The initial idea was to do a constant pulsing (on a NSTimer) but on testing a single pulse looked at lot cleaner on the UI.
EDIT:
if you try to call removeAnimationForKey for a key that does not exist what happens, currently I am checking the UILabel tag before I do the remove, do I need to do this?
I think anyway, it is not wrong, it might be that the framework does it by its own way, but doing it manually doesn't harm it, as long as addAnimation method is a pair method with removeAnimation...

How to perform kCATransitionPush animation without any transparency / fade effects [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
iPhone CATransition adds a fade to the start and end of any animation?
I'm trying to duplicate the "slide up from the bottom" animation that [UIViewController presentModalViewController:animated:] performs but without calling it because I don't want a modal view.
The below core animation code comes very close but appears to be changing transparency values of the views during it. At the start of the animation you can partially see through the view sliding up. By the middle/end of the animation the view we are sliding over is fully transparent so we can see behind it. I'd like both to remain fully opaque during this animation.
Any ideas on how to stop transparency changes in this code or to otherwise get the "slide up animation" I am looking for without requiring a modal view?
UIViewController *nextViewController = [[UIViewController alloc] autorelease];
nextViewController.view.backgroundColor = [UIColor redColor];
CATransition *animation = [CATransition animation];
animation.duration = 3.5;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.type = kCATransitionPush;
animation.subtype = kCATransitionFromTop;
[self.navigationController pushViewController:nextViewController animated:NO];
[self.navigationController.view.layer addAnimation:animation forKey:nil];
The simplest method is to simply use UIView beginAnimations/commitAnimations block. You can adjust the duration and curve by reviewing the docs.
{
// Adjust for orientation as necessary.
view.frame = CGRectMake(0,
-480,
320,
480);
[UIView beginAnimations:nil context:nil];
view.frame = CGRectMake(view.frame.origin.x,
0,
view.frame.size.width,
view.frame.size.height);
[UIView commitAnimations];
}
Docs here:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIView_Class/UIView/UIView.html
Instead of the CATransition, you could instead try using a CABasicAnimation that animates the position from off the top of the screen into the desired position.

Core Animation problem on iPhone

I'm new to iPhone development, and doing some experimentation with Core Animation. I've run into a small problem regarding the duration of the animation I'm attempting.
Basically, Ive got a view with two subviews, and I'm trying to animate their opacity so that one fades in while the other fades out. Problem is, instead of a gradual fade in/out, the subviews simply switch instantly to/from full/zero opacity. I've tried to adjust the animation duration with CATransaction with no noticable effect. It's also not specific to animating opacity - animating position shows the same problem.
The code I'm using (inside a method of the superview) follows:
CALayer* oldLayer = ((UIView*) [[self subviews] objectAtIndex:0]).layer;
CALayer* newLayer = ((UIView*) [[self subviews] objectAtIndex:1]).layer;
[CATransaction begin];
[CATransaction setAnimationDuration:1.0f];
oldLayer.opacity = 0.0;
newLayer.opacity = 1.0;
[CATransaction commit];
Does anyone have an idea what the problem might be?
Noah's solution is the cleanest way to do what you want, but the reason why you're not seeing the animation is that implicit animations are disabled for CALayers that back UIViews. Because nothing is animating, the CATransaction is unable to set the duration for anything.
If you really wanted to perform this animation on the layer, you'd need to set up a manual CABasicAnimation to do this:
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
opacityAnimation.removedOnCompletion = NO;
opacityAnimation.fillMode = kCAFillModeForwards;
[oldLayer addAnimation:opacityAnimation forKey:#"opacity"];
[newLayer addAnimation:opacityAnimation forKey:#"opacity"];
[CATransaction begin];
[CATransaction setAnimationDuration:1.0f];
oldLayer.opacity = 0.0;
newLayer.opacity = 1.0;
[CATransaction commit];
(Note that I'm not sure if you can re-use the opacity animation here. You might need to create separate instances for each layer.)
Is there a reason you're using Core Animation over the generic UIView animation wrapper? It's pretty straightforward to do something like this with
UIView *oldView = [[self subviews] objectAtIndex:0];
UIView *newView = [[self subviews] objectAtIndex:1];
[UIView beginAnimations:#"swapViews" context:nil];
[UIView setAnimationDuration:1];
oldView.alpha = 0;
newView.alpha = 1;
[UIView commitAnimations];
Do you need to use an explicit CATransaction here?
The docs here imply that you don't.