I am trying to make a view slide from top to bottom. This is not a big deal, I used CABasicAnimation for this. The problem is when I want to remove the view. I use this animation.
CABasicAnimation *animation;
animation = [CABasicAnimation animationWithKeyPath:#"position"];
[animation setDelegate:self];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(self.view.layer.position.x, 0 - self.view.bounds.size.height / 2)];
animation.fromValue = [NSValue valueWithCGPoint:self.view.layer.position];
animation.autoreverses = NO;
animation.repeatCount = 0;
animation.duration = 0.25;
animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];
[self.view.layer addAnimation:animation forKey:#"moveX"];
Which animates the view perfectly. But, after the animation finishes, my view appears again. So I added this line :
[self.view removeFromSuperview];
Which removes the view, but with no animation. So I decided to add the remove code to this delegate:
-(void) animationDidStop:(CAAnimation *) animation finished:(bool) flag
So now, the animation works, the view disappears, but sometimes, I can see the view appear and disappear faster, is like after the animation, the view appears, then the animationDidStop delegate is called, and the view disappears, obviously this is awful. What am I doing wrong?
Might want to set these properties. They cause the presentation to be preserved at the end of the animation.
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
Then the "animationDidStop:" method can be used to remove the view at the end of the animation:
-(void) animationDidStop:(CAAnimation *) animation finished:(bool) flag {
if (animation == [containerView.layer animationForKey:#"moveX"]) {
// remove view here, add another view and/or start another transition
}
}
Well, according to the Apple sample "MoveMe", this (removedOnCompletion) should work, however, it doesn't seem to.
So, add these lines after your code:
[self.view.layer addAnimation:animation forKey:#"moveX"];
self.view.layer.position = [animation.toValue CGPointValue];
This ensures that after the animation runs, the layer is properly positioned.
I had this issue when performing several animations in an animation group. I had to set a couple properties on the animation group itself, not the individual animations.
CAAnimationGroup *animGroup = [CAAnimationGroup animation];
// MAKE SURE YOU HAVE THESE TWO LINES.
animGroup.removedOnCompletion = NO;
animGroup.fillMode = kCAFillModeForwards;
animGroup.animations = [NSArray arrayWithObjects:moveAnim, scaleAnim, nil];
animGroup.duration = tAnimationDuration;
[tImageView.layer addAnimation:animGroup forKey:nil];
This one bit me too. You want to set the animation's removedOnCompletion flag to NO. It defaults to YES, which means after the animation is complete, it's removed, and the view reverts to its initial state.
Setting the view to hidden as Rob suggests should do it.
For properties of properties I would stick with the ObjC 2.0 style like you already have in your code.
set.view.hidden = YES;
Can you set the view's hidden property to YES?
I think it would be:
self.view.hidden = YES;
But it might be:
[self.view setHidden:YES];
I turns out I am pretty lame at figuring out the proper way to access properties of properties.
Related
I've a screen where following kind of animation has to too be implemented.
The circles unfurl out of the bloom and then spread out to become the individual buttons. Slightly slower the first time it animates (1 sec), slight faster for the second time (.75s), and then faster for all subsequent times (.5s).
But I could not figure out what kind of animation has to be implemented. Any help would be appreciated.
What you describe is a complicated animation. What you should probably do is just break it down into its constituent parts:
Create circular buttons, e.g:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.backgroundColor = [UIColor ...];
button.frame = CGRectMake(...); // probably define this to be square, just off screen to the bottom
button.layer.cornerRadius = button.frame.size.height / 2.0;
button.layer.borderColor = [UIColor ...].CGColor; // white or black as appropriate
button.layer.borderWidth = 2.0;
[self.view insertSubview:button atIndex:0];
[buttons addObject:button];
Animate their appearance on the screen by animating each of the buttons from off screen to the appropriate vertical position using animateWithDuration. You could move them all together, or you might stagger them so they appear to fall into place, using a rendition that includes delay so you can delay each button a little more. Perhaps something like:
for (NSInteger idx = 0; idx < count; idx++) {
[UIView animateWithDuration:duration * (count - idx) / count
delay:duration * idx / count
options:0
animations:^{
UIButton *button = buttons[idx];
button.center = CGPointMake(button.center.x, idx * 50.0 + 100.0);
}
completion:^(BOOL finished) {
//do next step
}];
}
Unfurl the buttons by animating the frame of the button to be the full width that that row will eventually occupy. Again, you can just use animateWithDuration.
Animate the unrounding of the corners so they're square, like your final buttons. This is the one animation that you cannot easily do with animateWithDuration, but rather you might use CAKeyFrameAnimation:
[buttons enumerateObjectsUsingBlock:^(UIView *button, NSUInteger idx, BOOL *stop) {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"cornerRadius"];
animation.values = #[#(button.layer.cornerRadius), #(0.0)];
animation.duration = duration;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
if (idx == 0)
animation.delegate = self;
[button.layer addAnimation:animation forKey:nil];
}];
Your delegate's animationDidStop can initiate the next step of the animation.
Replace the wide button with one that is broken up into the right number of individual buttons for that row. You might want to make these be lined up edge to edge.
Do a final frame adjustment of the buttons so they end up with a little space in between them. Again, you can use animateWithDuration.
Replace the previously blank labels on the buttons with the final text strings. Note, the label on a button is not generally an animatable property, so if you'd like it to fade in nicely, you should use transitionWithView, specifying the common superview for all of those buttons and use an options value of UIViewAnimationOptionTransitionCrossDissolve.
If you look at each one of these steps, you can see that none is terribly complicated. Just break your complicated animation into separate steps, write one method for each animation, and have each trigger the next step of the animation.
UIButtons don't have a rounded rectangle look to them any more in iOS 7. Are you going to create a custom subclass of UIButton?
If so, I would suggest using CAAnimation. You should be able to attach a CALayer to each button and set it's borderColor, backgroundColor, borderWidth, and cornerRadius. When you want to animate the layer, just change the bounds of the layer and it will animate to the larger size.
To change the animation's duration or timing you could enclose your changes in a CATransaction. Something like this:
[CATransaction begin];
[CATransaction setAnimationDuration: 1.0];
CAMediaTimingFunction *linearTiming =
[CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionLinear];
[CATransaction setAnimationTimingFunction: linearTiming]
myButton1Layer.bounds = newButton1ounds;
myButton2Layer.bounds = newButton2ounds;
[CATransaction commit];
I'm currently performing a curl up animation by doing the following:
CATransition *animation = [CATransition animation];
animation.type = #"pageCurl";
animation.subtype = kCATransitionFromTop;
animation.fillMode = kCAFillModeForwards;
animation.duration = 1;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[[self.view.window layer] addAnimation:animation forKey:nil];
This animation will simply perform a page curl animation but in the end you are left looking at the same view that you started out with. No REAL transition ever occurred.
Now using animation blocks I know you can do something to the effect of:
[UIView transitionFromView:self.view
toView:aNewView // a view to transition to
duration:1
options:UIViewAnimationTransitionCurlUp|UIViewAnimationOptionCurveEaseIn
completion:NULL];
However, using animation blocks you are transitioning to a new view, aNewView, which is different from the CATransition animation above which reuses the same view (no real transition ever occurs). The problem with the animation block is that you have to actually create the new view to transition to which is cumbersome in my case because the view is rather complicated and I'd rather not create a UIView subclass.
Is there a way to perform the above CATransition animation using animation blocks while getting around the difficulties of having to rebuild a view or create a custom subclass?
Ok - so I figured it out for anyone who is interested. It's actually super simple. You can simply use the method: transitionWithView:duration:options:animations:completion:
For example:
[UIView transitionWithView:self.view
duration:1
options:UIViewAnimationOptionTransitionCurlUp|UIViewAnimationCurveEaseIn
animations:^{ // do anything you want here }
completion:NULL];
That's it. This will show a transition animation back to the same view. You can make any changes you want to the new view (maybe display some new text, whatever...) in the animation block.
I hope this helps someone out down the line...
Currently I am having a issue switching views using Core Animation. I want to fade through black switching to my next view.
Right now it does not do anything besides lose touch events from my original view.
What am I doing wrong in the code below?
Edit1 Code:
- (void)changeView1ToView2 {
CABasicAnimation *fadeout= [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadeout setDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:)];
[fadeout setToValue:[NSNumber numberWithFloat:0]];
[fadeout setDuration:0.5];
[[self.view layer] addAnimation:fadeout forKey:#"alpha"];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
[self.view addSubview:self.view2.view];
self.view2.view.frame = [UIScreen mainScreen].bounds;
[self.view2.view setAlpha:0];
CABasicAnimation *fadein = [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadein setToValue:[NSNumber numberWithFloat:1.0]];
[fadein setDuration:0.5];
[[self.view2.view layer] addAnimation:fadein forKey:#"alpha"];
}
Ok I added self, look at my new code above. view2 is a UIViewController, thats why I am doing .view after it. The app is only going to be available on iOS 5 or up so thats not a problem. But what I am trying to achieve is switching views using Core Animation, and have each UIViewController manage their own views. I am just switching views using Core Animation instead of usual means.
If you're looking to have only one root view on screen at one time (and by the looks of that call to [UIScreen mainScreen].bounds, you are), then I'd suggest swapping the views at the UIWindow level. Without animations, this would look something like this:
// Assuming UIViewControllers called view1 and view2 as members of some
// (non-UIViewController) controller class (self, in this case)
//and that view1.view is in the application's window's subviews collection
[self.view1.view removeFromSuperview];
[[UIApplication sharedApplication].delegate.window addSubview:self.view2.view];
In terms of not seeing the views actually swap, you need to ensure that your animation preserves the changes you make during the animation. Specifically, you need to set the following:
myAnimation.fillMode = kCAFillModeForwards;
myAnimation.removedOnCompletion = NO;
Your methods can then be adjusted to take all this into account. For example, your animationDidStop:finished: method might look like this:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[self.view1.view removeFromSuperview];
[self.view2.view setAlpha:0];
[[UIApplication sharedApplication].delegate.window addSubview:self.view2.view];
CABasicAnimation *fadein = [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadein setToValue:[NSNumber numberWithFloat:1.0]];
[fadein setDuration:0.5];
fadein.fillMode = kCAFillModeForwards;
fadein.removedOnCompletion = NO;
[[self.view2.view layer] addAnimation:fadein forKey:#"alpha"];
}
You may need to muck around with it a bit to ensure that everything is firing correctly.
I have an animation that works perfectly on the first invocation. But if I want to animate the very same layer again, using the same code, it completes immediately and the animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag is invoked on the delegate with the flag value NO.
Here is the code that adds the animation:
imageView.hidden = NO;
CAKeyframeAnimation* animationOpacity =
[CAKeyframeAnimation animationWithKeyPath:#"opacity"];
...
animationOpacity.duration = 2.0;
animationOpacity.removedOnCompletion = YES;
animationOpacity.delegate = self;
[imageView.layer addAnimation:animationOpacity forKey:#"someKey"];
and this is the delegate action:
-(void) animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
imageView.hidden = YES;
}
BTW, Initially the imageView is visible in the XIB.
Turns out to be a combination of setting view.hidden = YES in the callback and calling the animation code from the parent's viewWillApear. Once I moved the animation code call into parent's viewDidApear instead, things started behaving as expected.
Are you adding the animation again before calling it a second time? If you aren't then you should set removedOnCompletion = NO
animationOpacity.removedOnCompletion = NO;
I have a UILabel that displays the current date for the current view of my scroll view. When I scroll this view to the left (to the left pages), I'd like this label to change to the day before, with a crossing effect between the 2 dates.
I take as reference the fading effect of the Springboard when you scroll to the Spotlight page. I think this is similar but I have no idea how to implement it !
Where do I put the code, do I have to use 2 different UILabel or maybe CoreAnimation ? When do I change the date ?
Thanks in advance !
In your scrollViewDidScroll: delegate method, check if the scroll view's contentOffset indicates that the user has scrolled to another page.
If so, change the label's text to the new date. It won't animate yet, but do this before you try the animation.
For the animation, add a CATransition to the label's layer and configure it to your liking:
// Create a transition animation and add it to the label's layer:
CATransition *transition = [CATransition animation];
transition.type = kCATransitionFade;
transition.duration = 0.25;
[self.dateLabel.layer addAnimation:transition forKey:#"textTransition"];
// Change the label's text as before:
self.dateLabel.text = newDateString;
This effect is a little different from the fading of the Springboard icons (since those fade continuously as you scroll further to the Spotlight page) but I think my approach is more appropriate for your application. To emulate the Springboard effect, you should indeed use two separate labels (with the same frame) and change their opacity values as the scroll view's contentOffset changes.
Using Core Animation with two labels would work just fine. Changing the property of a Core Animation layer result in a smooth transition by default, so you can use that to your advantage. For example:
- (void)crossfadeDateLabels
{
date1Label.layer.opacity = 0.0f;
date2Label.layer.opacity = 1.0f;
}
Or you can use the explicit animation model if you want more control:
- (void)crossfadeDateLabels
{
CABasicAnimation *date1Anim = [CABasicAnimation animationWithKeyPath:#"opacity"];
date1Anim.duration = 1.0f;
date1Anim.fromValue = [NSNumber numberWithFloat:1.0f];
date1Anim.toValue = [NSNumber numberWithFloat:0.0f]
// Do something similar for date2Anim...
[date1Label.layer addAnimation:date1Anim forKey:nil];
[date2Label.layer addAnimation:date2Anim forkey:nil];
}