How to animate view after using UIModalPresentationCurrentContext - ios

I was looking around for a solution on how to wrap a view in a dialog in iOS when I came across this post, which has this line:
vc.modalPresentationStyle = UIModalPresentationCurrentContext;
It solves my problem of basically creating/imitating a dialog but it does not animate upon transition as mentioned in the post. So what is the simplest way to get the slide up animation?
ps.I would ask this as a sub question in that post but I do not have 50 rep comment :(

Well, once your view has been shown, you can do pretty much any animation you want in it. You can do a simple [UIView animateWithDuration] kind of deal, but I would personally use a CATransition for this, it's relatively simple.
The Way of the QuartzCore
First, I'm gonna assume that the view you're presenting is transparent, and there's another view inside that behaves as the dialog. The view controller that will be presented, let's call it PresentedViewController and holds the dialog property for the view within.
PresentedViewController.m
(Needs to be link against QuartzCore.h)
#import <QuartzCore/QuartzCore.h>
#implementation PresentedViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (animated)
{
CATransition *slide = [CATransition animation];
slide.type = kCATransitionPush;
slide.subtype = kCATransitionFromTop;
slide.duration = 0.4;
slide.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
slide.removedOnCompletion = YES;
[self.dialog.layer addAnimation:slide forKey:#"slidein"];
}
}
Getting Fancy
The good thing about this, is that you can create your own custom animations, and play around with other properties.
CABasicAnimation *animations = [CABasicAnimation animationWithKeyPath:#"transform"];
CATransform3D transform;
// Take outside the screen
transform = CATransform3DMakeTranslation(0, self.view.bounds.size.height, 0);
// Rotate it
transform = CATransform3DRotate(transform, M_PI_4, 0, 0, 1);
animations.fromValue = [NSValue valueWithCATransform3D:transform];
animations.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
animations.duration = 0.4;
animations.fillMode = kCAFillModeForwards;
animations.removedOnCompletion = YES;
animations.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0 :0.0 :0 :1];
[self.dialog.layer addAnimation:animations forKey:#"slidein"];
Here, the view will be moved outside of screen by the translation, then rotated, and it will slide in, back to its original transform. I also modified the timing function to provide a smoother curve.
Consider that I just scraped the surface of what's possible with CoreAnimation, I've been on this road for three years now, and I've grown to like CAAnimation for all the things it does.
Storyboard pro-tip: You can wrap this up very nicely if you build your own custom UIStoryboardSegue subclass.

Related

Using Slide In & Slide Out Animations for Subview

I will start off by explaining that I have seen many questions and answers regarding this type of feature, but I am still having problems implementing it myself. I am using ARC, and am not using auto-layout or storyboard. I define my layouts with constraints in code, so the way I have been trying to implement my animation is a little different. Lastly, this is an iPad application.
To the specific problem at hand, I have a subview that starts off hidden but appears when an action takes place. I would like this subview to use the hidden feature, but slide in and out after it appears and before it is hidden. So far, I have gotten halfway there and am able to get the view to slide in without issue. Below is the code that accomplishes this.
detailView.hidden = NO;
// Perform Animation - Slide In
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.duration = kAnimationTimeout;
animation.removedOnCompletion = NO;
animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(800.0, 0.0, 1.0)];
[detailView.layer addAnimation:animation forKey:nil];
However, I have been unsuccessful with trying getting the view to slide out before it is hidden. Below is the code that I added to attempt at completing this feature.
// Perform Animation - Slide Out
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.duration = kAnimationTimeout;
animation.removedOnCompletion = NO;
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-800.0, 0.0, 1.0)];
[detailView.layer addAnimation:animation forKey:nil];
detailView.hidden = YES;
The result I get is that the view simply disappears like it was hidden, which it always did. Do I need to remove one animation that is added to a view before I add a different animation? Or is my CATransform3DMakeTranslation incorrectly defined?
Turns out detailView.hidden was being called before the animation started. I resolved this by adding a selector with a delay that contained a method to hide my view.
[self performSelector:#selector(hideDetailView) withObject:nil afterDelay:.40];

Core animation layer is acting weird when presenting a modal view controller. CATransform3D issue

I am trying to create a custom UIView transition. Basically when a certain modal view is presented then the views it is covering up move into the background.
To achieve this I am using Core animation to manipulate the CATransform3D on the layer of the view I am moving to the background then presenting the modal view on top of that.
To move the view into the background I am creating a CABasicAnimation to animate the change in the CATransform3D like this
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
CATransform3D toTransform = CATransform3DIdentity;
toTransform.m34 = 1.0f / -500.0f;
toTransform = CATransform3DTranslate(toTransform, 0.0f, 0.0f, -40.0f);
[animation setFromValue:[NSValue valueWithCATransform3D:CATransform3DIdentity]];
[animation setToValue:[NSValue valueWithCATransform3D:toTransform]];
[animation setDuration:1.0f];
self.menuView.layer.transform = toTransform;
[self.menuView.layer addAnimation:animation forKey:#"moveBackAnimation];
self.menuView is the view I am moving back.
This works great until I add in the modal view.
RVLAddViewController *addViewController = [[RVLAddViewController alloc] initWithNibName:#"RVLAddViewController" bundle:nil];
[self presentViewController:addViewController animated:YES completion:nil];
I call this code right under the animation when a button is pressed.If this code isn't present them every thing works as expected the view looks like it is just fading into the distance and then it stays there.
When the modal view is presented then instead of gradually fading into the distance, it shoots down to the smaller size and moves into the top left hand corner.
I'll upload a sample application as soon as I can, github is down right now. edit No need -a solution has been given! /edit
I messed around with this for a while, and it looks like it has to do with auto layout. If you turn that off, it works as you would expect.

UIView Animation slide in

I am trying to get my UIView to slide in from the right in the viewDidLoad method here's what I've got.
CGRect menuFrame = self.menu.frame;
menuFrame.origin.y = menuFrame.size.height+200;
[UIView animateWithDuration:1
delay:0.05
options: UIViewAnimationCurveEaseIn
animations:^{
self.menu.frame = menuFrame;
}
completion:^(BOOL finished){
NSLog(#"Animation Complete!");
}];
I am not sure what has gone wrong here I appreciate any help greatly.
This isn't what you asked, but it is worth saying anyway: viewDidLoad is a bad place for this code. viewDidLoad is called because the view controller has obtained its view, not because that view has appeared or will soon appear in the interface. It might soon appear in the interface, which is why your code has seemed to work up until now, but it might not.
The correct place for this code is probably viewWillAppear or viewDidAppear. You probably want to call this code only once, though, the first time the view appears. If you want to prevent the code from being called, because you've already animated menu into view on an earlier viewDidAppear call, simply include a conditional check to see whether menu already has a frame within the visible view.
By the way, another reason for avoiding things having to do with frames and bounds in viewDidLoad is that if your app launches into landscape and this is your root view, x and y are reversed.
It looks like you are trying to slide your menu in from the bottom as the Y position is pushed down initially by 200.
Usually you start by adding the view as a subview in its offscreen position and then set the onscreen position in the animation block.
And, make sure that you pass 1.0 as the animation duration.
use those transition.subtype for different effects
CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.subtype = kCATransitionFromLeft; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
//transition.subtype = kCATransitionFromTop; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
[appDelegate.navigation.view.layer addAnimation:transition forKey:nil];
viewController *DairyVC = [[viewController alloc] initWithNibName:#"viewController" bundle:nil];
[appDelegate.navigation pushViewController:DairyVC animated:YES];

addAnimation to UINavigationController cause UINavigationController's view (layer) displayed on the topmost layer

I am trying to add a cool animation effect to the UINavigationController, however I found once I do it it will mess up the other views displayed on top of other views which suppose to be on top of it.
e.g. navivc is a UINavigationController
overlayview is a subview of navivc's superview, and placed on top of navivc.
...
CATransform3D t1 = CATransform3DIdentity;
t1.m34 = 1.0/-900;
t1 = CATransform3DScale(t1, 0.7, 0.7, 0.3);
t1 = CATransform3DRotate(t1, 15.0f*M_PI/180.0f, 1, 0, 0);
CABasicAnimation *move = [CABasicAnimation animationWithKeyPath:#"transform" ];
//[move setFromValue:[NSNumber numberWithFloat:0.0f]];
[move setToValue:[NSNumber valueWithCATransform3D:t1]];
[move setDuration:0.3f];
move.removedOnCompletion = NO;
move.fillMode = kCAFillModeForwards;
[move setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
CAAnimationGroup *group = [CAAnimationGroup animation];
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
[group setDuration:0.3f];
[group setAnimations:[NSArray arrayWithObjects:move, nil]];
[navivc.view.layer addAnimation:move forKey:#"coolanimation"];
...
The animation is normal and correct, however now navivc's view show on top of overlayview.
Seemed the problem only happen on "transform" path, I tried other key paths, e.g. position, scale, translation etc., all those seemed to be correct. But the 3D transform messed up the view on top.
I would advise against animating a view controller's content view. Instead, create a container view inside the view controller's content view and animate that.
It isn't clear from your post where overlayView is in your view hierarchy, so it's hard to give you specific instructions on how to prevent your animation from covering your overlay view. You might try changing the zPosition of the overlayView's layer to make it appear above the layer you are animating.

UIView displays improperly after its layer has been animated

I have a problem I don't understand regarding UIViews and Core Animation. Here's what I want to do:
A small view is presented above a bigger view by putting it as one of its subviews.
When I click a button inside this view, the view should minimize and move to a specified CGRect.
Then the view is removed from its superview.
The first time I present, minimize-and-move and remove the view, everything works fine. But when I present the view again, it displays at the modified position (even though it's supposed to be set at the original position by a call to theView.frame = CGRectMake(600.0, 160.0, 339.0, 327.0);), while all the different responder elements (buttons, textviews, etc.) contained in the view act as if they were at the original position. It's like the view and the layer gets dissynchronized by the animation, and I do not know how to get them back in sync.
Having something like self.view.layer.frame = CGRectMake(600.0, 160.0, 339.0, 327.0); does not get anything right.
My minimize-and-move animation code is given below:
[CATransaction flush];
CABasicAnimation *scale, *translateX, *translateY;
CAAnimationGroup *group = [CAAnimationGroup animation];
group.delegate = delegate;
group.duration = duration;
scale = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
translateX = [CABasicAnimation animationWithKeyPath:#"transform.translation.x"];
translateY = [CABasicAnimation animationWithKeyPath:#"transform.translation.y"];
scale.toValue = [NSNumber numberWithFloat:0.13];
translateX.toValue = [NSNumber numberWithFloat:137.0];
translateY.toValue = [NSNumber numberWithFloat:-290.0];
group.animations = [NSArray arrayWithObjects: scale, translateX, translateY, nil];
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
[theView.layer addAnimation:group forKey:#"MyAnimation"];
How to get the layer back to the view after the animation?
What happens if you remove these two lines?
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
What you are telling core animation with those lines is that you want it to continue to display in the forward (final) state of the animation. Meanwhile, you didn't actually set the transform on the layer to have the properties you used for the animation. This would make things appear to be out of sync.
Now, the issue you're going to run into is that removing those lines will cause your transforms to revert back to the starting state when the animation has completed. What you need to do is actually set the transforms on the layer in order for them to hold their position when the animation completes.
Another option is to leave the two lines in and then actually explicitly remove the animation from the layer instead of setting the layer frame as you mentioned when you are ready to revert back to the original state. You do this with:
[theView.layer removeAnimationForKey:#"MyAnimation"];
The -removedOnCompletion property told the layer not to remove the animation when it finished. Now you can explicitly remove it and it should revert back.
HTH.

Resources