How to make a uibutton zoomout and then crossblur? - ios

I have got an UIButton animation implemented, as for now it zooms but I also want to cross fade the UIButton while it disappears.
This is my sample code for button animation.
(void)centerButtonAnimation{
CAKeyframeAnimation *centerZoom = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
centerZoom.duration = 1.5f;
centerZoom.values = #[[NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.5, 1.5, 1.5)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(4, 4, 4)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(5, 5, 5)]];
centerZoom.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
centerButton.transform = CGAffineTransformMakeScale(5, 5);
[centerButton.layer addAnimation:centerZoom forKey:#"buttonScale"];
[centerButton setUserInteractionEnabled:NO];
}

To chain the animations, you can set your class to be a delegate for the animation.
#property CAKeyframeAnimation *centerZoom;
- (void) centerButtonAnimation
{
self.centerZoom = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
// Set the delegate to this instance.
centerZoom.delegate=self;
centerZoom.duration = 1.5f;
centerZoom.values = #[[NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.5, 1.5, 1.5)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(4, 4, 4)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(5, 5, 5)]];
centerZoom.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
centerButton.transform = CGAffineTransformMakeScale(5, 5);
[centerButton.layer addAnimation:centerZoom forKey:#"buttonScale"];
[centerButton setUserInteractionEnabled:NO];
}
- (void) crossFadeAnimation
{
// Insert animation code for cross fade.
}
Then implement the delegate function to detect the end of the animation. If its the end of a zoom, start the cross fade.
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
if ((theAnimation == self.centerZoom) && flag){
[self crossFadeAnimation];
}
}

Related

How to get coordinates of view during animation?

I've created CAShapeLayer and added Bezier Cubic path(with addCurveToPoint:controlPoint1:controlPoint2: function) and added a little view on start point of that curve. After that I've created CAKeyFrameAnimation and moved that little view from start point of curve to the end point with animation. I want to get the center point of that view during animation. Any ideas?
CGPoint start, p, p1,p2;
start = CGPointMake(0, 150);
p = CGPointMake(300, 150);
p1 = CGPointMake(50, 225);
p2 = CGPointMake(250, 75);
[self.bezierPath moveToPoint:start];
[self.bezierPath addCurveToPoint:p
controlPoint1:p1
controlPoint2:p2];
NSLog(#"self bezierpath = %#",self.bezierPath);
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = self.bezierPath.CGPath;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.lineWidth = 3.f;
[self.yellowView.layer addSublayer:shapeLayer];
self.sliderThumb = [[UIView alloc]initWithFrame:(CGRect){0,0,20,20}];
self.sliderThumb.backgroundColor = [UIColor blackColor];
self.sliderThumb.center = CGPointMake(0, 150);
[self.yellowView addSubview:self.sliderThumb];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = #"position";
animation.duration = 4.f;
animation.path = self.bezierPath.CGPath;
[self.sliderThumb.layer addAnimation:animation forKey:nil];
CALayer *layer = self.sliderThumb.layer.presentationLayer;
NSLog(#"layer coordinates %f", layer.frame.origin.x);
`
The presentationLayer of the view's layer is a CALayer which captures where the layer is mid-animation.
For example, if you want to get updates frame by frame, set up a CADisplayLink and examine the presentationLayer in the display link's handler:
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.duration = 4.0f;
animation.path = self.bezierPath.CGPath;
animation.delegate = self; // so we know when animation finished
[self startDisplayLink]; // start display link
[self.sliderThumb.layer addAnimation:animation forKey:nil];
With:
#property (nonatomic, weak) CADisplayLink *displayLink;
And:
// Called when animation stops (assuming you set the `delegate` for the animation).
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
[self stopDisplayLink:self.displayLink];
}
/// Start the display link.
- (void)startDisplayLink {
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(handleDisplayLink:)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
self.displayLink = displayLink;
}
/// Stop the display link.
///
/// #parameter displayLink The display link to be stopped.
- (void)stopDisplayLink:(CADisplayLink *)displayLink {
[displayLink invalidate];
}
/// The display link handler.
///
/// Called once for each frame of the animation.
///
/// #parameter displayLink The display link being handled.
- (void)handleDisplayLink:(CADisplayLink *)displayLink {
CALayer *layer = self.sliderThumb.layer.presentationLayer;
NSLog(#"layer coordinates %f", layer.frame.origin.x);
}

Draw a circle on a Long Press event

I am drawing a circle on the screen as the user taps on a button. The animation duration is already set and also the from and to value are also set.
What I want to achieve is that somehow the animation should commence as the user long presses the button and continues till he is maintaining the tap on the screen i.e for the duration of Long Press.
As soon as the user lifts his finger the circle should stop to the point to where it has been completed till now.
Here is my code:
-(void)startCircularAnimation{
int radius = 50;
CAShapeLayer *circle = [CAShapeLayer layer];
// Make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Center the shape in self.view
circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor redColor].CGColor;
circle.lineWidth = 5;
// Add to parent layer
[self.view.layer addSublayer:circle];
// Configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 15.0;
drawAnimation.repeatCount = 1.0; // Animate only once..
// Animate from no part of the stroke being drawn to the entire stroke being drawn
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:counter/drawAnimation.duration];
// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
// Add the animation to the circle
[circle addAnimation:drawAnimation forKey:#"draw"];
}
This method performs animation and the from value is calculated from a timer which I started on the touch began case of the long press handler method. I am not able to get the perfect duration for the long press.
The long press event methods is something like this.
- (void)_handleLongPressGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer{
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
{
counter = 0;
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(incrementCounter) userInfo:nil repeats:YES];
}
case UIGestureRecognizerStateEnded:{
NSLog(#"State ended");
[timer invalidate];
break;
}
case UIGestureRecognizerStateCancelled:{
NSLog(#"State cancelled");
break;
}
case UIGestureRecognizerStateFailed:
{
break;
}
default:
break;
}
}
and the increment counter method is as follows
- (void)incrementCounter {
counter++;
[self startCircularAnimation];
}
This is not giving me the desired effect for drawing the circle till the user has his finger on the screen.
Please suggest something in the code to get the desired functionality.
Thanks in advance.
You'll want to follow apples guidelines https://developer.apple.com/library/ios/qa/qa1673/_index.html
So in your interface i'd declare the following
#interface ViewController ()
#property (nonatomic, strong) CAShapeLayer *circle;
#property (nonatomic, strong) CABasicAnimation *drawAnimation;
#property (strong, nonatomic) IBOutlet UIButton *circleButton;
#end
Then in view did load
- (void)viewDidLoad
{
[super viewDidLoad];
int radius = 50;
self.circle = [CAShapeLayer layer];
// Make a circular shape
self.circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Center the shape in self.view
self.circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
// Configure the apperence of the circle
self.circle.fillColor = [UIColor clearColor].CGColor;
self.circle.strokeColor = [UIColor redColor].CGColor;
self.circle.lineWidth = 5;
self.circle.strokeEnd = 0.0f;
// Add to parent layer
[self.view.layer addSublayer:_circle];
// Target for touch down (hold down)
[self.circleButton addTarget:self action:#selector(startCircleAnimation) forControlEvents:UIControlEventTouchDown];
// Target for release
[self.circleButton addTarget:self action:#selector(endCircleAnimation) forControlEvents:UIControlEventTouchUpInside];
/**
Don't start Animation in viewDidLoad to achive the desired effect
*/
}
Function to start the animation and resume it (probably needs a better name)
-(void)startCircleAnimation{
if (_drawAnimation) {
[self resumeLayer:_circle];
} else {
[self circleAnimation];
}
}
Function to end animation
-(void)endCircleAnimation{
[self pauseLayer:_circle];
}
Function to generate animation
- (void)circleAnimation
{
// Configure animation
self.drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
self.drawAnimation.duration = 10.0;
self.drawAnimation.repeatCount = 1.0; // Animate only once..
// Animate from no part of the stroke being drawn to the entire stroke being drawn
self.drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
// Set your to value to one to complete animation
self.drawAnimation.toValue = [NSNumber numberWithFloat:1.0f];
// Experiment with timing to get the appearence to look the way you want
self.drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
// Add the animation to the circle
[self.circle addAnimation:_drawAnimation forKey:#"draw"];
}
Pause and stop functions from apple
- (void)pauseLayer:(CALayer*)layer
{
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}
- (void)resumeLayer:(CALayer*)layer
{
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
}
The main point you'd want to take from this is that your not keeping account of how long the button is pressed for your just keeping account of the events that are sent from the button UIControlEventTouchDown and UIControlEventTouchUpInside
Edit:Gif
I think you should make your from value related to value of counter, this will make the drawing start from what it was left behind last time the user lift his finger.
You should also make your timer's time interval smaller, 1 second is too long, 0.1 second will be better.

Cannot control animation timing of a UITableViewCell's sublayer

The CAMediaTiming protocol defines property timeOffset which is supposed to set additional time offset of an animation or animated layer. By setting layer.speed = 0 it's possible to manually control animation timing by setting layer.timeOffset to a given value.
And I managed to do it in a regular view, however when I try to do it (set time offset of a layer) when the layer is a descendant of UITableViewCell's layer it has no effect.
Here's a quick snippet so you can see what I am trying to achieve and what doesn't work.
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[tableView deselectRowAtIndexPath:indexPath animated:NO];
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(0, 0, 80, 80);
layer.backgroundColor = [UIColor redColor].CGColor;
layer.opacity = .1f;
[cell.contentView.layer addSublayer:layer];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"opacity"];
animation.toValue = (id) [NSNumber numberWithFloat:1.f];
animation.duration = 1.f;
[layer addAnimation:animation forKey:nil];
layer.timeOffset = 0.7f;
layer.speed = 0.f;
}
You must allow the animation to start before you can control its timeOffset. Use a block with very small delay to do so:
CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:#"opacity"];
ba.removedOnCompletion = NO;
ba.duration = kAnimationDuration;
ba.autoreverses = YES;
ba.repeatCount = HUGE_VALF;
ba.fromValue = [NSNumber numberWithDouble:kInitialAlpha];
ba.toValue = [NSNumber numberWithDouble:kFinalAlpha];
[self.layer addAnimation:ba forKey:#"opacity"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
double offset = self.alphaOffset * ( kAnimationDuration / kNumAlphaOffsets );
self.layer.timeOffset = offset;
});

ios - cabasicanimation need to reset

I have 3 CABasicAnimation of 3 color bars which is following each other. After the third is completed, 3 bars will stay at their final position. Until here, everything is good, here is the code:
- (void)animationDidStop:(CABasicAnimation *)theAnimation finished:(BOOL)flag {
NSString* value = [theAnimation valueForKey:#"id"];
if([value isEqualToString:#"position1"]){
[self playVideoAtIndex:1];
}
else if([value isEqualToString:#"position2"]){
[self playVideoAtIndex:2];
}
else if([value isEqualToString:#"position3"]){
}
}
Before that, I created 3 animations like this:
-(void)createAnimationAtIndex:(NSInteger)index{
UILabel *label = (UILabel *)[barLabelArray objectAtIndex:index];
if(index==0){
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.delegate = self;
//SOMETHING HERE
[label.layer addAnimation:animation forKey:#"position1"];
}
else if(index==1){
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.delegate = self;
//SOMETHING HERE
[self.delegate startPlayVideoAtIndex:1];
[label.layer addAnimation:animation forKey:#"position2"];
}
else if(index==2){
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.delegate = self;
//SOMETHING HERE
[label.layer addAnimation:animation forKey:#"position3"];
}
}
So if I wait until all animations stop, when I come back, they will start animation properly again. But sometimes I need stoping the animation in the middle of it. And then when I come back and start the animation again, all is messed up. Here is code for stoping animations:
-(void)reset{
for(UILabel *label in barLabelArray){
[label.layer removeAllAnimations];
}
}
So do you know what I am doing wrong here and how to fix it? Thank you !!
There is a way pause and resume any animations. Here is several good strings which can help you if I understand correctly
- (void) pauseLayer
{
CFTimeInterval pausedTime = [label.layer convertTime:CACurrentMediaTime() fromLayer:nil];
label.layer.speed = 0.0;
label.layer.timeOffset = pausedTime;
}
- (void) resumeLayer
{
CFTimeInterval pausedTime = [label.layer timeOffset];
label.layer.speed = 1.0;
label.layer.timeOffset = 0.0;
label.layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [label.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
label.layer.beginTime = timeSincePause;
}

Multiple CALayer animations Executed at the Same time

I am trying to make an animation where two layers move to the side, scale down, and rotate a little bit in 3D, all at the same time (then move back with the layer previously at the bottom not on top). I tried several methods, but none seem to work.
I have the 3d transform animation like so:
perspectiveTransformLeft = CATransform3DIdentity;
perspectiveTransformLeft.m34 = 1.0 / 500;
perspectiveTransformLeft = CATransform3DRotate(perspectiveTransformLeft, 35.0f * M_PI / 360.0f, 0.0f, 1.0f, 0.0f);
I've tried adding a scale transform that didn't work:
perspectiveTransformLeft = CATransform3DMakeScale(0.75, 0.75, 1);
I've tried to scale the layer in an animation block, but that didn't work either:
[UIView animateWithDuration:1.0f
delay:0.0f
options: UIViewAnimationOptionCurveEaseInOut
animations:^{
endingLayer.frame = CGRectMake(20.0f, 0.0f, 724.0f, 538.0f);
switchViewBottom.layer.transform = perspectiveTransformRight;
}
completion:^(BOOL finished){
[delegate switchAnimationFinished];
}
];
I am at a loss. Can someone help me?
Do some reading on CAAnimationGroup and use CABasicAnimations instead.
That should held you achieve what you're after. I'll search for an example in my code (I previously used it) if you'll have issues implementing it.
Edit: Here's some code
typedef void (^animationCompletionBlock)(void);
typedef void (^animationStartedBlock)(void);
- (void)addAnimations:(NSArray *)animations withDuration:(CGFloat)animationDuration onView:(UIView *)aView {
animationStartedBlock startBlock = ^void(void) {
// Additional Animation start code here
};
animationCompletionBlock endBlock = ^void(void) {
// Additional animation completed code here
};
CAAnimationGroup *group = [CAAnimationGroup animation];
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
group.duration = animationDuration;
[group setAnimations:animations];
group.delegate = self;
[group setValue:startBlock forKey:#"animationStartedBlock"];
[group setValue:endBlock forKey:#"animationCompletionBlock"];
[aView.layer addAnimation:group forKey:#"yourAnimationName"];
}
This will have your completion blocks called in your delegate
// Animation Delegate
- (void)animationDidStart:(CAAnimation *)anim {
animationStartedBlock animationStartedBlock = [anim valueForKey:#"animationStartedBlock"];
if (animationStartedBlock) {
animationStartedBlock();
}
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
animationCompletionBlock animationCompleteBlock = [theAnimation valueForKey:#"animationCompletionBlock"];
if (animationCompleteBlock) {
animationCompleteBlock();
}
}
How you create the animations and add them to an array to pass to this method is up to you, depending on what animations you want.
This is an example for two scale / fade animations:
// Scale
- (CABasicAnimation *)scaleAnimationForImageView:(UIImageView *)imageView withDuration:(CGFloat)duration {
CGRect imageFrame = imageView.frame;
CABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:#"bounds.size"];
[resizeAnimation setToValue:[NSValue valueWithCGSize:CGSizeMake(40.0f, imageFrame.size.height * (40.0f / imageFrame.size.width))]];
resizeAnimation.fillMode = kCAFillModeForwards;
resizeAnimation.duration = duration;
resizeAnimation.removedOnCompletion = NO;
return resizeAnimation;
}
// Fade
- (CABasicAnimation *)fadeAnimationWithFinalOpacity:(CGFloat)opacity withDuration:(CGFloat)duration {
CABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadeOutAnimation setToValue:[NSNumber numberWithFloat:opacity]];
fadeOutAnimation.fillMode = kCAFillModeForwards;
fadeOutAnimation.removedOnCompletion = NO;
fadeOutAnimation.duration = duration;
return fadeOutAnimation;
}

Resources