I'm doing a simple animation of UIView height so that it reveals.
By default it seems to be revealing from top to bottom, and I want it to reveal bottom to top.
I have the UIView anchored to the bottom of the screen.
I'm sure it something simple i'm missing..... any tips?
Thanks
I really think the simplest way to accomplish this would be to animate BOTH the height and the y properties of the view. If they happen along the same curve, it should look completely seamless to the user. As you are animating the height to 0, also animate the y component to the original y + the original height.
UIView *view = ...;
float originalY = view.frame.origin.y;
float originalH = view.bounds.size.height;
[UIView animateWithDuration:1.2f delay:1.0f options:UIViewAnimationCurveEaseInOut animations:^{
view.frame = CGRectMake(view.frame.origin.x, (originalY + originalH), view.bounds.size.width, 0);
}completion:^(BOOL finished) {
NSLog(#"Animation is complete");
}];
I believe this would give the look and feel of a collapsing view. I haven't tried this out in code, but I see no reason why it wouldn't be possible like this.
hide under bottom
[self animateViewHeight:myView withAnimationType:kCATransitionFromBottom];
for reverse animation
[self animateViewHeight:myView withAnimationType:kCATransitionFromTop];
...
- (void)animateViewHeight:(UIView*)animateView withAnimationType:(NSString*)animType {
CATransition *animation = [CATransition animation];
[animation setType:kCATransitionPush];
[animation setSubtype:animType];
[animation setDuration:0.5];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[[animateView layer] addAnimation:animation forKey:kCATransition];
animateView.hidden = !animateView.hidden;
}
Like a dog with a bone I figured this out....
Instead of animating the frame height, I applied a transform to the view and set the anchor point of the layer.
//set the anchor point to the bottom of the view
[self setAnchorPoint:CGPointMake(0.5, 1.0) forView:hostView];
//Scale the height to close to zero
hostView.transform = CGAffineTransformMakeScale(1, 0.00001);
If I put 0 as the y scale, the view behaves weird.... at the end of the animation i just set it to hidden.
On the way back up I just use the Identity Transform (reset it)
hostView.transform = CGAffineTransformIdentity;
Note that changing my anchor point shifted the position of my view. See this post for the setAnchorPoint method which normalises the view after setting the anchorPoint
Changing my CALayer's anchorPoint moves the view
Instead you could try putting it in a view with clipsToBounds = YES and then animate it from the bottom to the middle of the view, like so:
viewToAnimate.frame = CGRectMake(viewToAnimate.frame.origin.x,
viewToAnimate.superview.frame.size.height,
viewToAnimate.frame.size.width,
viewToAnimate.frame.size.height);
[UIView animateWithDuration:0.5 animations:^{
viewToAnimate.center = viewToAnimate.superview.center;
}];
This way, you don't have to set the height to 0, and it solves any problems with autoresizing within the view.
As requested, this is the code that I'm using... I'm using a CAKeyFrameAnimation, which may be a bit more than what you're looking for. It would probably work the same with a CABasicAnimation, I'm just showing you this code because I already have it written.
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
springLayer = [[CALayer alloc] init];
springLayer.backgroundColor = [UIColor redColor].CGColor;
springLayer.anchorPoint = CGPointMake(0, 1);
springLayer.frame = CGRectMake(125, 285, 100, 115);
[springLayer setNeedsDisplay];
[self.layer addSublayer:springLayer];
[self test];
}
return self;
}
-(void)test {
CAKeyframeAnimation *heightAnim = [[CAKeyframeAnimation alloc] init];
heightAnim.duration = 3;
heightAnim.removedOnCompletion = NO;
heightAnim.fillMode = kCAFillModeForwards;
heightAnim.beginTime = CACurrentMediaTime() + 0.25;
NSMutableArray *v = [[NSMutableArray alloc] init];
NSMutableArray *t = [[NSMutableArray alloc] init];
float dest = 250;
float difference = 135;
while (difference > 1.0) {
[v addObject:[NSNumber numberWithFloat:dest-difference]];
[t addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
difference *= 0.7;
[v addObject:[NSNumber numberWithFloat:dest+difference]];
[t addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
difference *= 0.7;
}
heightAnim.values = v;
heightAnim.timingFunctions = t;
[springLayer addAnimation:heightAnim forKey:#"bounds.size.height"];
}
one way I've done it with an AdWhirlView, hide it below the screen, then animate it up;
AdWhirlView *adWhirlView = [AdWhirlView requestAdWhirlViewWithDelegate:self];
adWhirlView.delegate = self;
adWhirlView.frame = CGRectMake(0, 430+kAdWhirlViewHeight, kAdWhirlViewWidth, kAdWhirlViewHeight);
[self.parentViewController.view insertSubview:adWhirlView belowSubview:self.view];
[UIView beginAnimations:#"AdWhirlIn" context:nil];
[UIView setAnimationDuration:.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
adWhirlView.frame = CGRectMake(0, 430, kAdWhirlViewWidth, kAdWhirlViewHeight);
[UIView commitAnimations];
Related
Much like other apps, my app has a "welcome" page controller with a quick overview of the features. During this overview, I draw a UITabBar and have a circle show where the relevant feature is seen below:
I draw the circle using the following code that is executed every time a page is drawn:
double circleSize = 75;
[circleView removeFromSuperview];
circleView = [[UIView alloc] initWithFrame:CGRectMake(circleX,
circleY,
circleSize,
circleSize)];
circleView.layer.cornerRadius = (circleSize / 2);
circleView.layer.borderColor = [UIColor VancityTransitBlue].CGColor;
circleView.layer.borderWidth = 2;
I wish to have this circle appear to "breath" (grow slowly then shrink back to its original size). I use the exact code from my answer to this question:
[UIView animateWithDuration:1
delay:0
options:UIViewKeyframeAnimationOptionAutoreverse | UIViewKeyframeAnimationOptionRepeat
animations:^{
circleView.transform = CGAffineTransformMakeScale(1.5, 1.5);
}
completion:nil];
This circle is shared across three pages of the page controller and draws just fine. The animation works just fine on the first page, periodically works on the second page, and never works on the third page.
How can I have the animation play on every page for every circle view?
Here is a solution that works across all tabs. The animation can be moved around while in progress. You may want to use a better mechanism to manage the animation so that it can be cancelled efficiently. The code below is implemented in the Tab Controller. Ensure -showOverTabBarItem: and -hideCircleView are executed on main thread. It has been built, linked, ran and tested.
Show
-(void)showOverTabBarItem:(CGFloat)x {
[self hideCircleView];
self.circleView.frame =({
CGRect frame = self.circleView.frame;
frame.origin.x = x;
frame;
});
[self.view addSubview:self.circleView];
[UIView animateWithDuration:1
delay:0
options:UIViewKeyframeAnimationOptionAutoreverse | UIViewKeyframeAnimationOptionRepeat
animations:^{
self.circleView.transform = CGAffineTransformMakeScale(1.5, 1.5);
}
completion:nil];
}
Hide
-(void)hideCircleView
{
[self.circleView removeFromSuperview];
}
Initialize
Such as in viewDidLoad.
double circleSize = 75;
CGFloat circleY = self.view.bounds.size.height-(circleSize/2);
self.circleView = [[UIView alloc] initWithFrame:CGRectMake(0,
circleY,
circleSize,
circleSize)];
self.circleView.layer.cornerRadius = (circleSize / 2);
self.circleView.layer.borderColor = [UIColor blueColor].CGColor;
self.circleView.layer.borderWidth = 2;
Invoke
Pass the horizontal position to your animation: [self showOverTabBarItem: self.view.bounds.size.width/2];
Keep the view around
This is a critical step: when your -removeFromSuperView, you want to make sure that the object does not get recycled.
#interface TabController ()
#property (nonatomic, retain) UIView * circleView;
#end
Why have you alloc the circle every time a page drawn?
You can remove these lines (just need these for the first init):
if (circleView == nil) {
circleView = [[UIView alloc] initWithFrame:CGRectMake(circleX,
circleY,
circleSize,
circleSize)];
circleView.layer.cornerRadius = (circleSize / 2);
circleView.layer.borderColor = [UIColor VancityTransitBlue].CGColor;
circleView.layer.borderWidth = 2;
}
For add to another page: remove from current page
[circleView removeFromSuperview];
[circleView.layer removeAllAnimations];
and then add it to another page:
[self.view addSubView:circleView];
[UIView animateWithDuration:1
delay:0
options:UIViewKeyframeAnimationOptionAutoreverse | UIViewKeyframeAnimationOptionRepeat
animations:^{
circleView.transform = CGAffineTransformMakeScale(1.5, 1.5);
}
completion:nil];
I am animating UIView to have a feeling of book's page effect
#property (strong, nonatomic) UIView *insideView;
#property (strong, nonatomic) UIView *pageView;
#property (strong, nonatomic) UIView *backPageView;
#property (assign, nonatomic) CGRect cardFrame;
- (void)viewDidLoad
{
[super viewDidLoad];
[[self view] setBackgroundColor:[UIColor grayColor]];
//create frame for 2 test views
CGFloat size = 200.0;
_cardFrame = CGRectMake([[self view] center].x - size / 2, [[self view] center].y - size / 2 , size, size);
//lower view
_insideView = [[UIView alloc] initWithFrame: _cardFrame];
[_insideView setBackgroundColor:[UIColor redColor]];
//upper view
_pageView = [[UIView alloc] initWithFrame:_cardFrame];
[_pageView setBackgroundColor:[UIColor greenColor]];
//upper view back side
_backPageView = [[UIView alloc] initWithFrame:_cardFrame];
[_backPageView setBackgroundColor:[UIColor blueColor]];
[[self view] addSubview:_insideView];
[[self view] addSubview:_pageView];
[[self view] insertSubview:_backPageView belowSubview:_pageView];
//get layer of upper view and set needed property
CALayer *viewLayer = [_pageView layer];
CALayer *viewBackLayer = [_backPageView layer];
[viewLayer setAnchorPoint:(CGPoint){0.0 , 0.5}];
[viewLayer setFrame:_cardFrame];
[viewLayer setDoubleSided:NO];
[viewBackLayer setAnchorPoint:(CGPoint){0.0 , 0.5}];
[viewBackLayer setFrame:_cardFrame];
//create perspective
CATransform3D mt = CATransform3DIdentity;
mt.m34 = 1.0/-500.;
//create rotation
CATransform3D open = CATransform3DMakeRotation(3 * M_PI_4,0,-1, 0);
//create result transform
CATransform3D openTransform = CATransform3DConcat(open, mt);
[UIView animateWithDuration:1.0 animations:^
{
//close animation
[viewLayer setTransform:openTransform];
[viewBackLayer setTransform:openTransform];
} completion:^(BOOL finished)
{
[UIView animateWithDuration:1.0 animations:^
{
//close animation
[viewLayer setTransform:CATransform3DIdentity];
[viewBackLayer setTransform:CATransform3DIdentity];
}];
}];
}
But i want the UIView to animate from bottom to top,this code do animate but from left to right like book's page.
Please help!!
Thanks in advance.
try this.
//create frame for 2 test views
CGFloat size = 200.0;
_cardFrame = CGRectMake([[self view] center].x - size / 2, [[self view] center].y - size / 2 , size, size);
//lower view
_insideView = [[UIView alloc] initWithFrame: _cardFrame];
[_insideView setBackgroundColor:[UIColor redColor]];
//upper view
_pageView = [[UIView alloc] initWithFrame:_cardFrame];
[_pageView setBackgroundColor:[UIColor greenColor]];
//upper view back side
_backPageView = [[UIView alloc] initWithFrame:_cardFrame];
[_backPageView setBackgroundColor:[UIColor blueColor]];
[[self view] addSubview:_insideView];
[[self view] addSubview:_pageView];
[[self view] insertSubview:_backPageView belowSubview:_pageView];
//get layer of upper view and set needed property
CALayer *viewLayer = [_pageView layer];
CALayer *viewBackLayer = [_backPageView layer];
[viewLayer setAnchorPoint:(CGPoint){0.5 , 0.0}];
[viewLayer setFrame:_cardFrame];
[viewLayer setDoubleSided:NO];
[viewBackLayer setAnchorPoint:(CGPoint){0.5 , 0.0}];
[viewBackLayer setFrame:_cardFrame];
//create perspective
CATransform3D mt = CATransform3DIdentity;
mt.m34 = 1.0/-500.;
//create rotation
CATransform3D open = CATransform3DMakeRotation(3 * M_PI_4,1,0,0);
//create result transform
CATransform3D openTransform = CATransform3DConcat(open, mt);
[UIView animateWithDuration:1.0 animations:^
{
//close animation
[viewLayer setTransform:openTransform];
[viewBackLayer setTransform:openTransform];
} completion:^(BOOL finished)
{
[UIView animateWithDuration:1.0 animations:^
{
//close animation
[viewLayer setTransform:CATransform3DIdentity];
[viewBackLayer setTransform:CATransform3DIdentity];
}];
}];
Here is the code
- (void)viewDidLoad
{
[super viewDidLoad];
[[self view] setBackgroundColor:[UIColor grayColor]];
//create frame for 2 test views
CGFloat size = 200.0;
_cardFrame = CGRectMake([[self view] center].x - size / 2, [[self view] center].y - size / 2 , size, size);
//lower view
_insideView = [[UIView alloc] initWithFrame: _cardFrame];
[_insideView setBackgroundColor:[UIColor redColor]];
//upper view
_pageView = [[UIView alloc] initWithFrame:_cardFrame];
[_pageView setBackgroundColor:[UIColor greenColor]];
//upper view back side
_backPageView = [[UIView alloc] initWithFrame:_cardFrame];
[_backPageView setBackgroundColor:[UIColor blueColor]];
[[self view] addSubview:_insideView];
[[self view] addSubview:_pageView];
[[self view] insertSubview:_backPageView belowSubview:_pageView];
//get layer of upper view and set needed property
CALayer *viewLayer = [_pageView layer];
CALayer *viewBackLayer = [_backPageView layer];
// need to change the anchor point to center of top edge.
// that is the point in which you need to rotate.
[viewLayer setAnchorPoint:(CGPoint){0.5 , 0.0}];
[viewLayer setFrame:_cardFrame];
[viewLayer setDoubleSided:NO];
[viewBackLayer setAnchorPoint:(CGPoint){0.5 , 0.0}];
[viewBackLayer setFrame:_cardFrame];
//create perspective
CATransform3D mt = CATransform3DIdentity;
mt.m34 = 1.0/-500.;
//need to rotate in X axis. so changed arguments.
CATransform3D open = CATransform3DMakeRotation(3 * M_PI_4,1,0, 0);
//create result transform
CATransform3D openTransform = CATransform3DConcat(open, mt);
[UIView animateWithDuration:1.0 animations:^
{
//close animation
[viewLayer setTransform:openTransform];
[viewBackLayer setTransform:openTransform];
} completion:^(BOOL finished)
{
[UIView animateWithDuration:1.0 animations:^
{
//close animation
[viewLayer setTransform:CATransform3DIdentity];
[viewBackLayer setTransform:CATransform3DIdentity];
}];
}];
}
The two key parts in that page flip is the rotation transform and the anchor point. If you are unfamiliar with this then I suggest that you look at the documentation to see how they work, I will only give a brief explanation.
Rotation transform
You can see that CATransform3DMakeRotation takes 4 arguments, the first is the angle of the rotation and the next three are the axis of the rotation. You are rotating around (0,-1, 0) which is the y axis (vertical on the screen). To instead cause a rotation around the horizontal axis (x) you should change the last 3 arguments to 1, 0, 0 (or maybe -1, I can't seem to remember that on the top of my head).
CATransform3D open = CATransform3DMakeRotation(3.0*M_PI_4, // angle
1, 0, 0); // axis
Anchor point
The anchor point specifies how the layer is drawn relative to its position. Both x and y in the anchor point range from 0 to 1 so (0.5, 0.5) is in the center of the layer, no matter the size. In your case the anchor point is (0.0, 0.5) meaning center left edge. You should probably change that to (0.5, 0.0) center top edge or (0.5, 1.0) center bottom edge depending on where you want the anchor point to be for your page flip.
viewLayer.anchorPoint = CGPointMake(0.5, 0.0); // center top edge
set Uiview Frame in view did Load then set on your event button clicked. view animaate Bottom to top
CGRect optionsFrame = AddNOTEview.frame;
optionsFrame.origin.y=300;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.50];
AddNOTEview.frame = optionsFrame;
[self.view addSubview:AddNOTEview];
I have a problem when playing multiple UIView animation blocks.
What I have is a game with a lot of buttons, when you press a button it spawns an UILabel which animates to the top of the screen where is gets "added" to the total point amount, this is done by using 3 nested blocks.
This works well an lets me spawn as many point labels as possible with animations on them.
//setup the label
UILabel *pointIndicator = [[UILabel alloc]initWithFrame:CGRectMake(button.button.frame.origin.x, button.button.frame.origin.y, 60, 30)];
pointIndicator.backgroundColor = [UIColor clearColor];
pointIndicator.font = [UIFont boldSystemFontOfSize:25];
pointIndicator.alpha = 0;
pointIndicator.layer.shadowOffset = CGSizeMake(1.0f, 1.0f);
pointIndicator.layer.shadowOpacity = 0.6;
pointIndicator.layer.shadowRadius = 1;
[pointIndicators addObject:pointIndicator];
[self.view addSubview:pointIndicator];
CGPoint scoreLabelPosition = [self.view convertPoint:self.totalScoreLabel.frame.origin fromView:self.totalScoreLabel.superview];
CGSize scoreLabelSize = [self.totalScoreLabel.text sizeWithFont:self.totalScoreLabel.font];
[UILabel animateWithDuration:0.3 delay:0 options:UIViewAnimationCurveEaseIn animations:^{
//Make label appear and move it above button
CGRect frame = pointIndicator.frame;
frame.origin.y -= 30;
pointIndicator.frame = frame;
pointIndicator.alpha = 1;
}completion:^(BOOL finished){
[UILabel animateWithDuration:0.4 delay:0 options:UIViewAnimationCurveEaseInOut animations:^{
//Move the label next to the score label
NSInteger YPosition = 0;
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad){
YPosition = 15;
}else{
YPosition = 2;
}
CGRect frame = pointIndicator.frame;
frame.origin.x = scoreLabelPosition.x + self.totalScoreLabel.frame.size.width/2 + scoreLabelSize.width/2 + 5;
frame.origin.y = scoreLabelPosition.y - self.totalScoreLabel.frame.size.height/2 + YPosition;
pointIndicator.frame = frame;
}completion:^(BOOL finished){
[UILabel animateWithDuration:0.5 animations:^{
pointIndicator.alpha = 0;
}completion:^(BOOL finished){
//Animate point label to increase a bit in size
CABasicAnimation *pointAnim = [CABasicAnimation animationWithKeyPath:#"transform"];
pointAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
pointAnim.duration = 0.1;
pointAnim.repeatCount = 1;
pointAnim.autoreverses = YES;
pointAnim.removedOnCompletion = YES;
pointAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.5, 1.5, 1.0)];
[self.totalScoreLabel.layer addAnimation:pointAnim forKey:nil];
[pointIndicator removeFromSuperview];
[pointIndicators removeObject:pointIndicator];
}];
}];
}];
However, when the game is ended all the buttons animate out of the screen and an image view grows in size out of the middle, each are using 1 animation block.
The problem is that if an UILabel is animating across the screen at the same time as the game ends it cancels the button animation AND the growing image animation. If no UILabels are spawned everything plays as it should. I tried to cancel and remove all the point labels before playing the other animations, but with no luck. It only seems to work if all the previous animations have been completed normally in advance. This is how I remove them:
for(UIView *pointView in pointIndicators){
[pointView.layer removeAllAnimations];
[pointView removeFromSuperview];
}
[pointIndicators removeAllObjects];
[self.totalScoreLabel.layer removeAllAnimations];
all the views which are animated are subviews of the same UIView. I noticed that if I make the point labels subviews of the buttons all the animations can play at the same time.
I cant seem to figure out this behavior, is there some sort of conflict between the animations?
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...)
I would like to animate between two states of a view. Say, for example, I have a view with a label and when I change the text of the label an animation renders that change as a page flipping.
Now you can of course do this with a [UIView setAnimationTransition:forView:cache:]:
- (IBAction)nextPage {
[UIView beginAnimation:nil context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:pageView cache:NO];
label.text = #"page 2";
[UIView commitAnimations];
}
- (IBAction)previousPage {
[UIView beginAnimation:nil context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlDown forView:pageView cache:NO];
label.text = #"page 1";
[UIView commitAnimations];
}
...but then you cannot use your own custom animation, you are stuck to the built-in animations (they're nice but they're not tailored to my needs).
So the other option is to add a CAAnimation to the view's layer:
- (IBAction)nextPage {
CAAnimation *animation = [self animationWithDuration:1 forward:NO];
[pageView.layer addAnimation:animation forKey:#"pageTransition"];
label.text = #"page 2";
}
- (IBAction)previousPage {
CAAnimation *animation = [self animationWithDuration:1 forward:YES];
[pageView.layer addAnimation:animation forKey:#"pageTransition"];
label.text = #"page 1";
}
Then you are free to set whatever animation Core Animation enables you to do. This works well if I define a CATransition animation, for example a kCATransitionReveal: a view in the "page 2" state appears below the view in the "page 1" state as it slips out.
- (CAAnimation *)animationWithDuration:(float)duration forward:(BOOL)forward {
CATransition *animation = [CATransition animation];
[animation setType:kCATransitionReveal];
[animation setSubtype:forward?kCATransitionFromLeft:kCATransitionFromRight];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
animation.duration = duration;
return animation;
}
But when I define the animation to be for example a CABasicAnimation, only one state of the view is visible.
- (CAAnimation *)animationWithDuration:(float)duration forward:(BOOL)forward {
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -1000;
transform = CATransform3DRotate(transform, M_PI, 0.0f, 1.0f, 0.0f);
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
if(forward) {
animation.fromValue = [NSValue valueWithCATransform3D:transform];
} else {
animation.toValue = [NSValue valueWithCATransform3D:transform];
}
animation.duration = duration;
return animation;
}
Instead, I would like the view in the "page 1" state to remain visible until the end of the animation while the view in the "page 2" state comes into frame, exactly as it behaves with a transition animation.
Of course, I could always mess with duplicating the view and having one appear as a sibling view while I animate the frontmost one and remove it from superview on completion... but there must be a much more straight way to achieve this rather simple animation effect without messing with the views.
Probably something to tell the layer, but then I don't know what, and that's where I need your help, guys :)
Thanks!
I recently did a custom slide transition between subviews in a controller. My approach was to grab a bitmap of the view-out and the view-in, add them to a view and animate that view. At the end you can just remove the transition view and show the view-in. Here's a snippet the transition method:
-(void) transitionToView:(UIView *)viewIn lastView:(UIView *)viewOut slideRight:(BOOL)isRight {
UIGraphicsBeginImageContext(viewOut.frame.size);
UIImageView *bitmapOut = [[UIImageView alloc] initWithFrame: viewOut.frame];
[viewOut.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewOutImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
bitmapOut.image = viewOutImage;
UIImageView *bitmapIn = [[UIImageView alloc] initWithFrame: viewIn.frame ];
bitmapIn.frame = CGRectMake(isRight ? 320 : -320, bitmapIn.frame.origin.y, bitmapIn.frame.size.width, bitmapIn.frame.size.height);
UIGraphicsBeginImageContext(viewIn.frame.size);
[viewIn.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewInImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
bitmapIn.image = viewInImage;
[self.view addSubview:transitionContainer];
[transitionContainer addSubview:bitmapIn];
[transitionContainer addSubview:bitmapOut];
[self removeAllViews];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5f];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(swapViewsAtEndOfTransition:)];
transitionContainer.frame = CGRectMake(isRight ? -320 : 320, 0, 640, 411);
[UIView commitAnimations];
[bitmapOut release]; bitmapOut = nil;
[bitmapIn release]; bitmapIn = nil;}
Then, in the swapViewsAtEndOfTransition selector you can update the views accordingly.
Well, you can create a layer, set it content to an image representing your view final state, than apply the animation to this layer. When the animation is done you can remove the layer and switch view state.
I think this will work better than instancing whole view.
Years have passed but I guess I found a solution for you. Just use kCATransitionPush instead of kCATransitionReveal. Like this:
- (CAAnimation *)animationWithDuration:(float)duration forward:(BOOL)forward {
CATransition *animation = [CATransition animation];
[animation setType:kCATransitionPush];
[animation setSubtype:forward?kCATransitionFromLeft:kCATransitionFromRight];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
animation.duration = duration;
return animation;
}