Animating UILabel opacity using CALayer? - iphone

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...

Related

Multiple CAAnimations On Different Views Simultaneously

I'm trying execute a couple of animations at the same time. One is transitioning from one uimageview to another and the other is animating translation.x of a label. The label resides on top of uiimageview.
But what I get is either translation working fine and transition happens immediately, or the transitioning -based on hidden property - also applies to my label which should only be shifted (it also goes from hidden to visible). I can't use caanimationgroup because they apply to different views.
//CAKeyFrameAnimation for sliding the label
...
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"transform.translation.x"];
NSArray *xValues = #[[NSNumber numberWithFloat:myLabel.bounds.origin.x],
[NSNumber numberWithFloat:myLabel.bounds.origin.x + screenHalf],
[NSNumber numberWithFloat:myLabel.bounds.origin.x + screenHalf * 4]];
[anim setValues:xValues];
NSMutableArray *timeFrames = [NSMutableArray array];
CGFloat timeStep = 1.0 / ([xValues count] - 1);
for (NSInteger i = 0; i < [xValues count]; i++)
{
[timeFrames addObject:[NSNumber numberWithFloat:timeStep * i]];
}
[anim setKeyTimes:timeFrames];
[anim setDuration:duration];
[anim setFillMode:kCAFillModeForwards];
[anim setRemovedOnCompletion:FALSE];
[myLabel.layer addAnimation:anim forKey:nil];
...
//Transitioning from uiimageview to another one
...
CATransition *transition = [CATransition animation];
[transition setDuration:duration];
[transition setType:kCATransitionFade];
//These two are uiimageviews i'm switching from and to
initial.hidden = TRUE;
next.hidden = FALSE;
//Initial and next are subviews of container which itself is a subview of viewcontroller's main view
[container.layer addAnimation:transition forKey:#""];
If I call the animations like above the transition occurs immediately and label slides across screen correctly. If I change the last line to:
[self.view.layer addAnimation:transition forKey:#""];
Then hidden animation also applies to myLabel. What is the fix for combining animations like above, and also more elaborately what is the cause?
I would wrap the entire code in a CATransaction to group the different CAAnimations into a single group.
Psuedo-code for using this with CAKeyFrameAnimation would look like:
[CATransaction begin];
// set completion block if you want
[CATransaction setCompletionBlock:^{ NSLog(#"I'm done"); }];
// start a keyframe animation
CAKeyframeAnimation *key1 = .....
// start another keyframe animation block
CAKeyframeAnimation *key2 = ......
// Maybe do a basic animation
CABasicAnimation *anim1 = .....
// close out all the animations and have them start
[CATransaction commit];

semi page curl animation background view color?

In my project I'm using page animation.
My problem is that I want to show red colour on the background of curled view. How do I achieve that?
Thanks in advance.
CATransition *animation = [CATransition animation];
[animation setDelegate:self]; [animation setDuration:0.75];
[animation setTimingFunction:UIViewAnimationCurveEaseInOut];
animation.type = #"pageUnCurl";
animation.fillMode = kCAFillModeBackwards;
animation.startProgress = 0.65;
[animation setRemovedOnCompletion:NO];
[[self view] exchangeSubviewAtIndex:[self.view.subviews count]-2 withSubviewAtIndex:3];
[[self view] exchangeSubviewAtIndex:[self.view.subviews count]-3 withSubviewAtIndex:2];
[[[self view] layer] addAnimation:animation forKey:#"pageCurlAnimation"];
This is how I change my view.
This is possible if you use CALayer as the background page when animation is happening.
topPageReverseOverlay = [[CALayer alloc] init];
topPageReverseOverlay.backgroundColor = [[[UIColor redColor] colorWithAlphaComponent:0.8] CGColor];
If you are using layers then when app going to background process then you can loose the curl effect of that particular layer.
If you want complete control over the display of what's behind a partial curl, you can set it as a separate VC. Here's an example:
EndPartialCurlViewController *nextViewController = [[EndPartialCurlViewController alloc] init];
[nextViewController setModalTransitionStyle:UIModalTransitionStylePartialCurl];
[[self navigationController] presentViewController:nextViewController animated:YES completion:nil];
You can then build any color, views, buttons, etc. you want into this EndPartialCurlViewController.
I believe the answer for your question can be found in this thread..
Underside color of curling view using UIViewAnimationTransitionCurlUp
after lot of surfing i found that there is one way by using filters and layes we can achieve this one but that one undocumented method and If you are using layers then when app going to background process then you can loose the curl effect of that particular layer..

Animating transitions of two subviews simultaneously with CATransition

I have my main view, and when a user taps a 'Show' button I want two things to happen:
Fade in a black UIView with alpha 0.5 covering the entire parent
view. I'll refer to this as darkenBackground
Slide the view of a second view controller
(self.secondViewController.view) on top of darkenBackground
When a user is finished, they tap 'Done' and the following happens:
darkenBackground fades out and is removed from the superview.
self.secondViewController.view slides out of the screen in the
bottom direction and is removed from the superview.
All animations working simultaneously.
So far, I've been able to achieve the first part (sort-of), transition-in. This works fine. But when the user taps 'Done' the transition-out does not work as required. The problems I'm faced with are:
darkenBackground and self.secondViewController.view fade out and are removed, but
self.secondViewController.view wasn't supposed to fade out! I tried animating only the second view but it simply disappears without animation.
When a user taps 'Show' for a second time
(transition-in), the view from the previous transition
appears before the animation takes place. It doesn't look like it
was removed from the superview previously.
Here is my code:
When a user taps 'Show':
- (IBAction)showSecondView {
darkenBackground = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 460)];
[darkenBackground setBackgroundColor:[UIColor blackColor]];
[darkenBackground setAlpha:0.5];
self.secondViewController.view.frame = CGRectMake(0, 0, 320, 460);
self.secondViewController.view.backgroundColor = [UIColor clearColor];
[CATransaction begin];
CATransition *animation = [CATransition animation];
[animation setDuration:0.5];
[animation setType:kCATransitionFade];
CATransition *animation2 = [CATransition animation];
[animation2 setDuration:0.5];
[animation2 setType:kCATransitionMoveIn];
[animation2 setSubtype:kCATransitionFromTop];
[animation2 setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[self.view addSubview:darkenBackground];
[self.view addSubview:self.secondViewController.view];
[[self.view layer] addAnimation:animation forKey:#"darkenBkgFadeIn"];
[[self.secondViewController.view layer] addAnimation:animation2 forKey:#"ShowSecondView"];
[CATransaction commit];
}
When a user taps 'Done' (A function defined in the secondViewController.m class where it refers to the main view controller variables through delegate):
- (IBAction)hideSecondView {
[CATransaction begin];
CATransition *animation = [CATransition animation];
[animation setDuration:0.5];
[animation setType:kCATransitionFade];
CATransition *animation2 = [CATransition animation];
[animation2 setDuration:0.5];
[animation2 setType:kCATransitionPush];
[animation2 setSubtype:kCATransitionFromBottom];
[animation2 setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[self.myParentController.darkenBackground removeFromSuperview];
[self.myParentController.secondViewController.view removeFromSuperview];
[[self.myParentController.view layer] addAnimation:animation forKey:#"darkenBkgFadeOut"];
[[self.myParentController.secondViewController.view layer] addAnimation:animation2 forKey:#"RemoveSecondView"];
[CATransaction commit];
}
Ultimately I want to show the view in the same way that the TWTweetComposeViewController modal view appears, where the background darkens and the controller appears. I could just use a modal view controller, but the issue I have with that is that when presenting the view controller it only 'appears' and does not slide from the bottom (since I require a semi-transparent background, I cannot use the default context for the modal view).
Cannot figure this one out, would appreciate some guidance.
I assume that self.myParentController.view used in -hideSecondView is the same view as self.view in -showSecondView. If this is the case, then you're applying a transition to the whole view here:
[[self.myParentController.view layer] addAnimation:animation forKey:#"darkenBkgFadeOut"];
You added self.secondViewController.view as a subview of self.view in -showSecondView, so when you later perform a transition on self.myParentController.view, it's going to apply the transition to all its subviews (i.e., self.secondViewController.view) as well.
Instead of applying the transition to self.myParentController.view in -hideSecondView, apply it directly to darkenBackground:
[[self.myParentController.darkenBackground layer] addAnimation:animation forKey:#"darkenBkgFadeOut"];
If I recall correctly, you can apply CATransitions when layers are added/removed to/from a view. If not, then you'll have to wrap darkenBackground in a separate container view and perform the transitions on that so it doesn't include self.secondViewController.view when you do the fade out.

Animating a pulsing UILabel?

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

How to replicate the Scale Up Animation when an app starts regularly (push app icon on HomeScreen) on an iPhone

i want to replicate the animation when an app starts on iPhone.
There is the first view scaling up from 50 % to 100% i think.
later I want to use this as a transition between 2 views.
Any ideas how to replicate, or is there a ready to use solution from apple in the sdk?
Thank you very much :)
You can do the same thing with CABasicAnimation and CAAnimationGroup, I actually thought that Core Animation over UIKit Animations was smoother and It gives you more control.
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.removedOnCompletion = YES;
CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeAnimation.fromValue = [NSNumber numberWithFloat:0.0];
fadeAnimation.toValue = [NSNumber numberWithFloat:1.0];
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scaleAnimation.fromValue = [NSNumber numberWithFloat:0.5];
scaleAnimation.toValue = [NSNumber numberWithFloat:1.00];
animationGroup.animations = [NSArray arrayWithObjects:fadeAnimation, scaleAnimation, nil];
[self.layer addAnimation:animationGroup forKey:#"fadeAnimation"];
self.layer.opacity = 1.0;
"There's always more then one way to skin a cat"
You can apply a scale transform to the UIView and then animate it back to it's normal state.
// Set the the view's scale to half
[viewToScale setTransform:CGAffineTransformMakeScale(0.5,0.5)];
// Set the transform back to normal (identity) in an animation when you're
// ready to animate
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.0];
[viewToScale setTransform:CGAffineTransformIdentity];
[UIView commitAnimations];
The trick will be you will need to set the view to the smaller scale in a separate run cycle from the animation or you won't see the view animate. So you can set the smaller scale and then call -performSelector:withObject:afterDelay: to actually perform the animation.