How to apply partial fade in-out in IOS? - ios

I have 2 images. One is in the back of the other. I want to fade-out the top image starting from the left corner. How can I do this?
Should I use gradient mask?

Here's a technique that will fade out whatever's in a CALayer, from top-left to bottom-right, revealing whatever is underneath the CALayer.
Let's say we're going to fade out the layer of a UIImageView called self.fadeView.
We'll create a CAGradientLayer and use it as self.fadeView.layer.mask. The gradient will go from alpha=0 (transparent) to alpha=1 (opaque) using four stops: 0, 0, 1, 1. Yes, two zeros and then two ones. When we want fadeView to be opaque, we'll set the stop locations to -1, -.5, 0, 1. That way the two alpha=0 stops are completely outside of the layer bounds. When we want fadeView to be transparent, we'll set the stop locations to 0, 1, 1.5, 2. That way the two alpha=1 stops are completely outside of the layer bounds. The CAGradientLayer will automatically animate changes to its stop locations, creating a cross-fade effect.
Here's the code:
#import "ViewController.h"
#implementation ViewController
#synthesize fadeView = _fadeView;
static NSArray *locations(float a, float b, float c, float d)
{
return [NSArray arrayWithObjects:
[NSNumber numberWithFloat:a],
[NSNumber numberWithFloat:b],
[NSNumber numberWithFloat:c],
[NSNumber numberWithFloat:d],
nil];
}
// In my test project, I put a swipe gesture recognizer on fadeView in my XIB
// with direction = Up and connected it to this action.
- (IBAction)fadeIn
{
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithDouble:2.0] forKey:kCATransactionAnimationDuration];
((CAGradientLayer *)self.fadeView.layer.mask).locations = locations(-1, -.5, 0, 1);
[CATransaction commit];
}
// In my test project, I put a swipe gesture recognizer on fadeView in my XIB
// with direction = Down and connected it to this action.
- (IBAction)fadeOut
{
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithDouble:2.0] forKey:kCATransactionAnimationDuration];
((CAGradientLayer *)self.fadeView.layer.mask).locations = locations(0, 1, 1.5, 2);
[CATransaction commit];
}
- (void)viewDidLoad
{
[super viewDidLoad];
CAGradientLayer *mask = [CAGradientLayer layer];
mask.frame = self.fadeView.bounds;
mask.colors = [NSArray arrayWithObjects:
(__bridge id)[UIColor clearColor].CGColor,
(__bridge id)[UIColor clearColor].CGColor,
(__bridge id)[UIColor whiteColor].CGColor,
(__bridge id)[UIColor whiteColor].CGColor,
nil];
mask.startPoint = CGPointZero; // top left corner
mask.endPoint = CGPointMake(1, 1); // bottom right corner
self.fadeView.layer.mask = mask;
[self fadeIn]; // initialize mask.locations
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end

Define your own CALayer subclass with its own actionForKey animation for the key contents.
#interface CustomLayer: CALayer
#end
#implementation CustomLayer
+ (id<CAAction>)actionForKey:(NSString*)event {
if ([event isEqualToString:#"contents"]) {
CATransition* basicAnimation = [CATransition animation];
basicAnimation.type = kCATransitionMoveIn;
basicAnimation.subtype = kCATransitionFromLeft;
return basicAnimation;
} else {
return nil;
}
}
#end
Doing like this, whenever you set the contents property of your CustomLayer object, the transition will be shown:
CustomLayer* layer = [CustomLayer layer];
layer.contents = <FIRST_CGIMAGEREF_HERE>;
...
layer.contents = <SECOND_CGIMAGEREF_HERE>;
As usual, you can wrap the property assignment in a transaction:
[CATransaction begin];
....
[CATransaction end];
if you want more control on the duration of the transition, but in any case the transition will be applied.
EDIT:
For the kind of transition you would like to have, you can have a look at this sample on GitHub. Look for the ShutterTransition. It is not exactly what you are looking for, but it could lead you along the right path.

Related

Why can't I animate a custom property on the UIView default backgorund layer?

I want to animate the UIView default background layer. In viewDidLoad I'm adding my custom property to animate to the view with:
self.layer.delegate = self;
[self.layer setValue:0 forKey:#"value"];
as described here.
The setter method of the value to change looks like this and updates the added custom property on the background layer:
- (void)setValue:(NSInteger)value
{
_value = value;
[self.layer setValue:[NSNumber numberWithInteger:value] forKey:#"value"];
[self setNeedsDisplay];
}
And in actionForLayer:forKey: overwritten in my view I'm returning an animation, as described here, which should animate from the current value to the new set value like this:
- (id<CAAction>)actionForLayer:(CALayer*)layer forKey:(NSString*)key
{
if([key isEqualToString:#"value"])
{
CAAnimation* action = (CAAnimation*)[self actionForLayer:layer forKey:#"backgroundColor"];
if(action != (CAAnimation*)[NSNull null])
{
CABasicAnimation* animation = [CABasicAnimation animation];
animation.keyPath = #"value";
animation.fromValue = [self.layer valueForKey:#"value"];
animation.toValue = [NSNumber numberWithInteger:_value];
animation.beginTime = action.beginTime;
animation.duration = action.duration;
animation.speed = action.speed;
animation.timeOffset = action.timeOffset;
animation.repeatCount = action.repeatCount;
animation.repeatDuration = action.repeatDuration;
animation.autoreverses = action.autoreverses;
animation.fillMode = action.fillMode;
animation.timingFunction = action.timingFunction;
animation.delegate = action.delegate;
[self.layer addAnimation:animation forKey:#"value"];
}
}
return [super actionForLayer:layer forKey:key];
}
And I draw my view content like this:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
[self drawARainbowUnicorn:context];;
}
From my view controller I do an block animation like this:
[UIView animateWithDuration:10.0 animations:
^{
self->_myView.value = self->_myView.maximumValue;
}];
What I see is that the value always appears as animated on the final position. What I would expect is that the value would animate to that position in the given time.
All the examples I found add a separate layer to animate like this
or this
or this and I'm aware of this StackOverflow question and some others I found.
I also had a look at this older sample project from Apple. They also create a CALayer subclass. But why is that needed?
I don't understand why the default background layer shouldn't do it as well. What do I miss here?

How do I create a growing iOS Button?

My friend gave me the following designs for iOS buttons, and I'm not sure the best way to implement this.
I need to make the reusable button shown below (in Objective-C).
I've tried:
Subclassing the button, but read that I shouldn't do that
Animating the border (while subclassed), but the border only goes inwards, so it seems like I need to animate the frame too
So how do I approach this? I'm assuming making a CustomButtonView class which has a button (composition) as well as an inner and outer circle view? How would I then animate that to grow? Would I have to animate the frame change too, or could I use insets?
What is the simplest code to make this work? Thanks!
Here Is the approach I took to create this:
Subclass UIView to create your custom button
Use UITapGestureRecognizer or touchesBegan, touchesEnded... for your interaction
Add two CALayer's for your foreground and background layers
Add your icon layer (This can be an UIImageView or any other way of displaying an image)
- (id)initWithIcon:(UIImage *)icon backgroundColor:(UIColor *)backgroundColor foregroundColor:(UIColor *)foregroundColor {
if (self = [super init]) {
// Background Layer Setup
_backgroundLayer = [CALayer new];
[_backgroundLayer setBackgroundColor:backgroundColor.CGColor];
[self.layer addSublayer:_backgroundLayer];
// Foreground Layer Setup
_foregroundLayer = [CALayer new];
[_foregroundLayer setBackgroundColor:foregroundColor.CGColor];
[self.layer addSublayer:_foregroundLayer];
// Icon Setup
_icon = [[UIImageView alloc] initWithImage:icon];
[_icon setContentMode:UIViewContentModeCenter];
[self addSubview:_icon];
UIGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(buttonTapped:)];
[self addGestureRecognizer:tapGesture];
}
return self;
}
- (void)setFrame:(CGRect)frame {
// Make sure super is called
[super setFrame:frame];
// Build the layout of backgroundLayer
[self.backgroundLayer setFrame:CGRectMake(frame.size.width*0.1, frame.size.width*0.1, frame.size.width*0.8, frame.size.width*0.8)];
[self.backgroundLayer setCornerRadius:frame.size.width*0.8/2];
// Build the layout of forgroundLayer
[self.foregroundLayer setFrame:CGRectMake(frame.size.width*0.05, frame.size.width*0.05, frame.size.width*0.9, frame.size.width*0.9)];
[self.foregroundLayer setCornerRadius:frame.size.width*0.9/2];
// Build the frame of your icon
[self.icon setFrame:CGRectMake(0, 0, frame.size.width, frame.size.width)];
}
- (void)buttonTapped:(UIGestureRecognizer*)gesture {
// Animate the foreground getting smaller
CABasicAnimation *foregroundFrameChange = [CABasicAnimation animationWithKeyPath:#"frame"];
foregroundFrameChange.fromValue = [NSValue valueWithCGRect:_foregroundLayer.frame];
foregroundFrameChange.toValue = [NSValue valueWithCGRect:CGRectMake(self.frame.size.width*0.1,self.frame.size.width*0.1, self.frame.size.width*0.8, self.frame.size.width*0.8)];
self.foregroundLayer.frame = CGRectMake(self.frame.size.width*0.1,self.frame.size.width*0.1, self.frame.size.width*0.8, self.frame.size.width*0.8);
// Animate the forground cornerRadius to stay rounded
CABasicAnimation *foregroundRadiusChange = [CABasicAnimation animationWithKeyPath:#"cornerRadius"];
foregroundRadiusChange.fromValue = [NSNumber numberWithDouble:self.foregroundLayer.cornerRadius];
foregroundRadiusChange.toValue = [NSNumber numberWithDouble:self.frame.size.width*0.8/2];
[self.foregroundLayer setCornerRadius:self.frame.size.width*0.8/2];
// Animate the background getting larger
CABasicAnimation *backgroundFrameChange = [CABasicAnimation animationWithKeyPath:#"frame"];
backgroundFrameChange.fromValue = [NSValue valueWithCGRect:self.backgroundLayer.frame];
backgroundFrameChange.toValue = [NSValue valueWithCGRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.width)];
self.backgroundLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.width);
// Animate the background cornerRadius to stay rounded
CABasicAnimation *backgroundRadiusChange = [CABasicAnimation animationWithKeyPath:#"cornerRadius"];
backgroundRadiusChange.fromValue = [NSNumber numberWithDouble:self.backgroundLayer.cornerRadius];
backgroundRadiusChange.toValue = [NSNumber numberWithDouble:self.frame.size.width/2];
[self.backgroundLayer setCornerRadius:self.frame.size.width/2];
// Group all the animations to run simultaneously
CAAnimationGroup *allAnimations = [CAAnimationGroup animation];
allAnimations.duration = 2;
allAnimations.animations = #[foregroundFrameChange, foregroundRadiusChange, backgroundFrameChange, backgroundRadiusChange];
allAnimations.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.layer addAnimation:allAnimations forKey:#"animate"];
// Create your button action callback here
}
This was a quick mock up and not a complete solution but it will give you something to play with.

Animate path of shape layer

I'm stumped by what I thought would be a simple problem.
I'd like to draw views connected by lines, animate the position of the views and have the connecting line animate too. I create the views, and create a line between them like this:
- (UIBezierPath *)pathFrom:(CGPoint)pointA to:(CGPoint)pointB {
CGFloat halfY = pointA.y + 0.5*(pointB.y - pointA.y);
UIBezierPath *linePath=[UIBezierPath bezierPath];
[linePath moveToPoint: pointA];
[linePath addLineToPoint:CGPointMake(pointA.x, halfY)];
[linePath addLineToPoint:CGPointMake(pointB.x, halfY)];
[linePath addLineToPoint:pointB];
return linePath;
}
-(void)makeTheLine {
CGPoint pointA = self.viewA.center;
CGPoint pointB = self.viewB.center;
CAShapeLayer *lineShape = [CAShapeLayer layer];
UIBezierPath *linePath=[self pathFrom:pointA to:pointB];
lineShape.path=linePath.CGPath;
lineShape.fillColor = nil;
lineShape.opacity = 1.0;
lineShape.strokeColor = [UIColor blackColor].CGColor;
[self.view.layer addSublayer:lineShape];
self.lineShape = lineShape;
}
It draws just how I want it to. My understanding from the docs is that I am allowed to animate a shape's path by altering it in an animation block, like this:
- (void)moveViewATo:(CGPoint)dest {
UIBezierPath *destPath=[self pathFrom:dest to:self.viewB.center];
[UIView animateWithDuration:1 animations:^{
self.viewA.center = dest;
self.lineShape.path = destPath.CGPath;
}];
}
But no dice. The view position animates as expected, but the line connecting to the other view "jumps" right away to the target path.
This answer implies that what I'm doing should work. And this answer suggests a CABasic animation, which seems worse to me since (a) I'd then need to coordinate with the much cooler block animation done to the view, and (b) when I tried it this way, the line didn't change at all....
// worse way
- (void)moveViewATo:(CGPoint)dest {
UIBezierPath *linePath=[self pathFrom:dest to:self.viewB.center];
[UIView animateWithDuration:1 animations:^{
self.viewA.center = dest;
//self.lineShape.path = linePath.CGPath;
}];
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:#"path"];
morph.duration = 1;
morph.toValue = (id)linePath.CGPath;
[self.view.layer addAnimation:morph forKey:nil];
}
Thanks in advance.
Thanks all for the help. What I discovered subsequent to asking this is that I was animating the wrong property. It turns out, you can replace the layer's shape in an animation, like this:
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:#"path"];
morph.duration = 1;
morph.fromValue = (__bridge id)oldPath.path;
morph.toValue = (__bridge id)newPath.CGPath;
[line addAnimation:morph forKey:#"change line path"];
line.path=linePath.CGPath;
I guess this is all you need:
#import "ViewController.h"
#interface ViewController ()
//the view to animate, nothing but a simple empty UIView here.
#property (nonatomic, strong) IBOutlet UIView *targetView;
#property (nonatomic, strong) CAShapeLayer *shapeLayer;
#property NSTimeInterval animationDuration;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//the shape layer appearance
self.shapeLayer = [[CAShapeLayer alloc]init];
self.shapeLayer.strokeColor = [UIColor blackColor].CGColor;
self.shapeLayer.fillColor = [UIColor clearColor].CGColor;
self.shapeLayer.opacity = 1.0;
self.shapeLayer.lineWidth = 2.0;
[self.view.layer insertSublayer:self.shapeLayer below:self.targetView.layer];
//animation config
self.animationDuration = 2;
}
- (UIBezierPath *)pathFrom:(CGPoint)pointA to:(CGPoint)pointB {
CGFloat halfY = pointA.y + 0.5*(pointB.y - pointA.y);
UIBezierPath *linePath=[UIBezierPath bezierPath];
[linePath moveToPoint: pointA];
[linePath addLineToPoint:CGPointMake(pointA.x, halfY)];
[linePath addLineToPoint:CGPointMake(pointB.x, halfY)];
[linePath addLineToPoint:pointB];
return linePath;
}
- (void) moveViewTo: (CGPoint) point {
UIBezierPath *linePath= [self pathFrom:self.targetView.center to:point];
self.shapeLayer.path = linePath.CGPath;
//Use CAKeyframeAnimation to animate the view along the path
//animate the position of targetView.layer instead of the center of targetView
CAKeyframeAnimation *viewMovingAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
viewMovingAnimation.duration = self.animationDuration;
viewMovingAnimation.path = linePath.CGPath;
//set the calculationMode to kCAAnimationPaced to make the movement in a constant speed
viewMovingAnimation.calculationMode =kCAAnimationPaced;
[self.targetView.layer addAnimation:viewMovingAnimation forKey:viewMovingAnimation.keyPath];
//draw the path, animate the keyPath "strokeEnd"
CABasicAnimation *lineDrawingAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
lineDrawingAnimation.duration = self.animationDuration;
lineDrawingAnimation.fromValue = [NSNumber numberWithDouble: 0];
lineDrawingAnimation.toValue = [NSNumber numberWithDouble: 1];
[self.shapeLayer addAnimation:lineDrawingAnimation forKey:lineDrawingAnimation.keyPath];
//This part is crucial, update the values, otherwise it will back to its original state
self.shapeLayer.strokeEnd = 1.0;
self.targetView.center = point;
}
//the IBAction for a UITapGestureRecognizer
- (IBAction) viewDidTapped:(id)sender {
//move the view to the tapped location
[self moveViewTo:[sender locationInView: self.view]];
}
#end
Some explanation:
For UIViewAnimation, the property value is changed when the
animation is completed. For CALayerAnimation, the property value is
never change, it is just an animation and when the animation is
completed, the layer will go to its original state (in this case, the
path).
Putting self.lineShape.path = linePath.CGPath doesn't work is
because self.linePath is a CALayer instead of a UIView, you
have to use CALayerAnimation to animate a CALayer
To draw a path, it's better to animate the path drawing with keyPath
strokeEnd instead of path. I'm not sure why path worked in the
original post, but it seems weird to me.
CAKeyframeAnimation (instead of CABasicAnimation or UIViewAnimation) is used to animate the view along the path. (I guess you would prefer this to the linear animation directly from start point to end point). Setting calculationMode to kCAAnimationPaced will give a constant speed to the animation, otherwise the view moving will not sync with the line drawing.

How do I simultaneously animate a UIImageView's image and the UIImageView itself?

The title may not be so clear, but what I want to do is to make the UIImageView display a series of images (sort of like a gif) and I do this by setting the animationImages to an array of images and then calling [imageView startAnimating];. This works fine. I also have code that moves the UIImageView around with a CABasicAnimation, and this animation code also works fine. However, when I try to both animate the images of the UIImageView and try to move the UIImageView around, the images of the UIImageView stop animating. Is there a workaround?
Here's my code for animating the content of the UIImageView:
self.playerSprite = [[UIImageView alloc] initWithImage:[self.player.animationImages objectAtIndex:0]];
[self.playerSprite setFrame:CGRectMake(self.center.x, self.center.y, self.tileSet.constants.TILE_WIDTH, self.tileSet.constants.TILE_HEIGHT)];
self.playerSprite.animationImages = self.player.animationImages;
self.playerSprite.animationDuration = self.tileSet.constants.animationDuration;
self.playerSprite.animationRepeatCount = 0; //Makes it repeat indefinitely
And here's my coding for animating the UIImageView with a CABasicAnimation:
float playerSpriteX = self.playerSprite.center.x;
float playerSpriteY = self.playerSprite.center.y;
CABasicAnimation *moveAnimation = [CABasicAnimation animation];
moveAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(playerSpriteX + TILE_WIDTH, playerSpriteY)];
[moveAnimation setDelegate:self];
[moveAnimation setFillMode:kCAFillModeForwards];
[moveAnimation setRemovedOnCompletion:NO];
[moveAnimation setDuration:MOVE_ANIMATION_DURATION];
[self.playerSprite.layer addAnimation:moveAnimation forKey:#"position"];
So the gif effect isn't working while the UIImageView's position is being animated. My question is how can I make it so the UIImageView cycles through an array of images while its position is being animated?
This is speculation and I haven't tested this idea at all, but have you considered implementing the image animation in the same fashion you're animating the position, with a CAKeyframeAnimation? You'd need to construct an array of CGImage objects from your UIImage array (to set the values property of the keyframe animation) but it looks like a pretty straightforward conversion:
CAKeyframeAnimation *imageAnimation = [CAKeyframeAnimation animation];
imageAnimation.calculationMode = kCAAnimationDiscrete; // or maybe kCAAnimationPaced
imageAnimation.duration = self.tileSet.constants.animationDuration;
imageAnimation.repeatCount = HUGE_VALF;
// the following method will need to be implemented to cast your UIImage array to CGImages
imageAnimation.values = [self animationCGImagesArray];
[self.playerSprite.layer addAnimation:imageAnimation forKey:#"contents"];
-(NSArray*)animationCGImagesArray {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:[self.player.animationImages count]];
for (UIImage *image in self.player.animationImages) {
[array addObject:(id)[image CGImage]];
}
return [NSArray arrayWithArray:array];
}
I've just done something similar. The code I used looks like this:
CGRect r = ziv.frame;
r.origin.x += WrongDistance;
[ziv startAnimating];
[UIView animateWithDuration:3.0 animations:^(void){
[ziv setFrame:r];
} completion:^(BOOL finished){
[ziv stopAnimating];
if (finished){
// not canceled in flight
if (NumWrong == MaxWrong)
[self endOfGame:NO];
else
[self nextRound:self];
}
}];
Perhaps the issue you're running into is because both animations are on the same thread?
Your "basic" problem starts on this line:
CABasicAnimation *moveAnimation = [CABasicAnimation animation];
A CABasicAnimation object uses only a single keyframe. That means that the content of the layer that you're animating is drawn once, and then that image is used for the duration of the animation.
Using a CAKeyframeAnimation as Seamus suggests is one way to deal with the problem -- a keyframe animation will redraw the content of the animated layer multiple times, so drawing successive images for each keyframe will animate the content as well as position.
This is not a good way to make a sprite. I could insist that you should be using OpenGL, but there may be no need to go that far; you can do it all with Core Animation of a layer, and I think you'll be better off doing that than trying to use a full-fledged UIImageView.
Using Core Animation of a layer, I was able to make this PacMan sprite animate across the screen while opening and closing his mouth; isn't that the sort of thing you had in mind?
Here is a video showing the animation!
http://www.youtube.com/watch?v=WXCLc9ww8MI
And yet the actual code creating the animation is extremely simple: just two layer animations (Core Animation), one for the changing image, the other for the position.
You should not award this answer the bounty! I am now merely echoing what Seamus Campbell said; I'm just filling out the details of his answer a little.
Okay, so here's the code that generates the movie linked above:
- (void)viewDidLoad {
[super viewDidLoad];
// construct arr, an array of CGImage (omitted)
// it happens I've got 5 images, the last being the same as the first
self.images = [arr copy];
// place sprite into the interface
self.sprite = [CALayer new];
self.sprite.frame = CGRectMake(30,30,24,24);
self.sprite.contentsScale = [UIScreen mainScreen].scale;
[self.view.layer addSublayer:self.sprite];
self.sprite.contents = self.images[0];
}
- (void)animate {
CAKeyframeAnimation* anim =
[CAKeyframeAnimation animationWithKeyPath:#"contents"];
anim.values = self.images;
anim.keyTimes = #[#0,#0.25,#0.5,#0.75,#1];
anim.calculationMode = kCAAnimationDiscrete;
anim.duration = 1.5;
anim.repeatCount = HUGE_VALF;
CABasicAnimation* anim2 =
[CABasicAnimation animationWithKeyPath:#"position"];
anim2.duration = 10;
anim2.toValue = [NSValue valueWithCGPoint: CGPointMake(350,30)];
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = #[anim, anim2];
group.duration = 10;
[self.sprite addAnimation:group forKey:nil];
}

UIview Flip darkens the Views

I've been implementing a simple FlipView in iOS : A UIView that contains two subviews, displaying one at a time, and when you click on it, it flips them.
I'm using the following to animate the flipping.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
#synchronized(self){
if(!self.flipping){
self.flipping = YES;
UIView *toView = self.currentView == self.primaryView ? self.secondaryView : self.primaryView;
[UIView transitionFromView:self.currentView toView:toView duration:self.speed options:UIViewAnimationOptionTransitionFlipFromLeft|UIViewAnimationOptionCurveEaseInOut completion:^(BOOL finished) {
[self.currentView removeFromSuperview];
self.currentView = toView;
self.flipping = NO;
}];
}
}
}
Pretty straight forward, right ?
But what bugs me is that, while the views are flip, the flipped content is darkened. Which shows, against a light background.
Would anyone knows a solution to have the exact same animation, but without the darkening (<= is that even a word ?)
Thanks in advance !
PS : I'm targeting IOS 5 and above.
I recently had a problem with similar symptoms and I was adding a subview over and over again else where in my code whenever I committed a certain action. Maybe you are doing something similar? When your touches end, are you doing something else to your flipped content? You probably need to remove the subviews being added IF that is your problem.
I succeeded, getting inspiration in the code I found here http://www.mycodestudio.com/blog/2011/01/10/coreanimation/ (and he, himself, took inspiration from http://www.mentalfaculty.com/mentalfaculty/Blog/Entries/2010/9/22_FLIPPIN_OUT_AT_NSVIEW.html)
Anyway, what I do spin between two views.
- (void)flip{
#synchronized(self){
if(!self.flipping){
self.flipping = YES;
UIView *bottomView = self.currentView == self.primaryView ? self.secondaryView : self.primaryView;
CALayer *top = self.currentView.layer;
CALayer *bot = bottomView.layer;
CAAnimation *topAnimation = [self flipAnimationWithDuration:self.speed/2.0 forLayerBeginningOnTop:YES scaleFactor:1];
CAAnimation *bottomAnimation = [self flipAnimationWithDuration:self.speed/2.0 forLayerBeginningOnTop:NO scaleFactor:1];
CGFloat zDistance = 1500.0f;
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1. / zDistance;
top.transform = perspective;
bot.transform = perspective;
topAnimation.delegate = self;
[CATransaction setCompletionBlock:^{
[top removeAllAnimations];
[self.currentView removeFromSuperview];
self.currentView = bottomView;
[self addSubview:bottomView];
[CATransaction setCompletionBlock:^{
self.flipping = NO;
[bot removeAllAnimations];
}];
[CATransaction begin];
[bot addAnimation:bottomAnimation forKey:#"flip"];
[CATransaction commit];
}];
[CATransaction begin];
[top addAnimation:topAnimation forKey:#"flip"];
[CATransaction commit];
}
}
}
-(CAAnimation *)flipAnimationWithDuration:(NSTimeInterval)aDuration forLayerBeginningOnTop:(BOOL)beginsOnTop scaleFactor:(CGFloat)scaleFactor
{
// Rotating halfway (pi radians) around the Y axis gives the appearance of flipping
CABasicAnimation *flipAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
CGFloat startValue = beginsOnTop ? 0.0f : M_PI/2;
CGFloat endValue = beginsOnTop ? -M_PI/2 : 0.0f;
flipAnimation.fromValue = [NSNumber numberWithDouble:startValue];
flipAnimation.toValue = [NSNumber numberWithDouble:endValue];
// Shrinking the view makes it seem to move away from us, for a more natural effect
// Can also grow the view to make it move out of the screen
CABasicAnimation *shrinkAnimation = nil;
if (scaleFactor != 1.0 ) {
shrinkAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
shrinkAnimation.toValue = [NSNumber numberWithFloat:scaleFactor];
// We only have to animate the shrink in one direction, then use autoreverse to "grow"
shrinkAnimation.duration = aDuration * 0.5;
shrinkAnimation.autoreverses = YES;
}
// Combine the flipping and shrinking into one smooth animation
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = [NSArray arrayWithObjects:flipAnimation, shrinkAnimation, nil];
// As the edge gets closer to us, it appears to move faster. Simulate this in 2D with an easing function
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:beginsOnTop?kCAMediaTimingFunctionEaseIn:kCAMediaTimingFunctionEaseOut];
animationGroup.duration = aDuration;
// this really means keep the state of the object at whatever the anim ends at
// if you don't do this then it reverts back to the original state (e.g. brown layer)
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.removedOnCompletion = NO;
return animationGroup;
}
The two views are named primaryView and secondaryView. You can use any view, (ImageView, text view...)

Resources