I would like to push a ViewController with a custom modal animation. I would prefer to use the "Cover Vertical" animation. Is there a UIViewAnimationOption for "Cover Vertical"?
[self.navigationController pushViewController:myViewController animated:NO];
[UIView transitionWithView:self.navigationController.view duration:1 options:UIViewAnimationOptionCurveEaseIn animations:nil completion:nil];
Replace UIViewAnimationOptionCurveEaseIn with ... ? Or is there a better way to do this?
You need to use an Animation Controller. The code to set this up looks like this:
- (id)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController (UIViewController *)source
{
return self.animationController;
}
- (id )animationControllerForDismissedController:(UIViewController *)dismissed {
self.animationController.isPresenting = NO;
return self.animationController;
}
Then you need to use the following method determine if you are presenting or dismissing:
-(void)animateTransition:(id)transitionContext{
if(self.isPresenting){
[self executePresentationAnimation:transitionContext];
}
else{
[self executeDismissalAnimation:transitionContext];
}
}
Finally, you define your animations, the following code will push the view in from the top as modal view:
-(void)executePresentationAnimation:(id)transitionContext{
UIView* inView = [transitionContext containerView];
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[inView addSubview:toViewController.view];
CGPoint centerOffScreen = inView.center;
centerOffScreen.y = (-1)*inView.frame.size.height;
toViewController.view.center = centerOffScreen;
[UIView animateWithDuration:self.presentationDuration delay:0.0f usingSpringWithDamping:0.4f initialSpringVelocity:6.0f options:UIViewAnimationOptionCurveEaseIn animations:^{
toViewController.view.center = inView.center;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
and when you dismiss the reverse should happen:
-(void)executeDismissalAnimation:(id)transitionContext{
UIView* inView = [transitionContext containerView];
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[inView insertSubview:toViewController.view belowSubview:fromViewController.view];
CGPoint centerOffScreen = inView.center;
centerOffScreen.y = (-1)*inView.frame.size.height;
[UIView animateKeyframesWithDuration:self.dismissalDuration delay:0.0f options:UIViewKeyframeAnimationOptionCalculationModeLinear animations:^{
[UIView addKeyframeWithRelativeStartTime:0 relativeDuration:0.5 animations:^{
CGPoint center = fromViewController.view.center;
center.y += 50;
fromViewController.view.center = center;
}];
[UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{
fromViewController.view.center = centerOffScreen;
}];
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
Related
I am trying to use a custom segue to perform a kind of zoom animation.
When the transition is executed, the sourceViewController goes black, then the zoom occurs.
Tried also to set the pushViewController: into the completion block but the transition is not executed at all.
- (void)perform {
UIViewController *sourceViewController = (UIViewController *) self.sourceViewController;
UIViewController *destinationViewController = (UIViewController *) self.destinationViewController;
[destinationViewController.view setTransform:CGAffineTransformMakeScale(0.5,0.5)];
[destinationViewController.view setAlpha:0.0];
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationCurveEaseOut
animations:^{
[destinationViewController.view setTransform:CGAffineTransformMakeScale(1.0,1.0)];
[destinationViewController.view setAlpha:1.0];
[sourceViewController.navigationController pushViewController:destinationViewController animated:NO];
}
completion:^(BOOL finished){
}];
}
What I am doing wrong ?
It feels kludgy, but you can try adding the destinationViewController.view as a subview before your animation and then when the animation is done, remove it and push it back on without animation. Solves the black screen before the transition, but perhaps not perfect, depending upon what you want to do with the navigation bar, but maybe closer:
[sourceViewController.view addSubview:destinationViewController.view];
[destinationViewController.view setFrame:sourceViewController.view.window.frame];
[destinationViewController.view setTransform:CGAffineTransformMakeScale(0.5,0.5)];
[destinationViewController.view setAlpha:1.0];
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationCurveEaseOut
animations:^{
[destinationViewController.view setTransform:CGAffineTransformMakeScale(1.0,1.0)];
[destinationViewController.view setAlpha:1.0];
}
completion:^(BOOL finished){
[destinationViewController.view removeFromSuperview];
[sourceViewController.navigationController pushViewController:destinationViewController animated:NO];
}];
Note, effective iOS 7, you would use custom transitions. For more information, see WWDC 2013 Custom Transitions Using View Controllers.
For example, if trying to do a custom transition with navigation controller, the first view controller would specify itself as the delegate of the navigation controller:
self.navigationController.delegate = self;
Then, it would specify the custom animators for push and pop, respectively:
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPush) {
return [[PushAnimator alloc] init];
} else {
return [[PopAnimator alloc] init];
}
}
And then you'd obviously implement these animators:
#interface PushAnimator : NSObject <UIViewControllerAnimatedTransitioning>
#end
#implementation PushAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.frame = fromViewController.view.frame;
toViewController.view.transform = CGAffineTransformMakeScale(0.5,0.5);
toViewController.view.alpha = 0.0;
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:0 animations:^{
toViewController.view.transform = CGAffineTransformIdentity;
toViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
[fromViewController.view removeFromSuperview];
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
#end
and
#interface PopAnimator : NSObject <UIViewControllerAnimatedTransitioning>
#end
#implementation PopAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
toViewController.view.frame = fromViewController.view.frame;
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:0 animations:^{
fromViewController.view.transform = CGAffineTransformMakeScale(0.5,0.5);
fromViewController.view.alpha = 0.0;
} completion:^(BOOL finished) {
[fromViewController.view removeFromSuperview];
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
#end
My code :
navigator.m
- (void)newPushPage:(UIViewController *)controller
{
[self pushViewController:controller animated:YES];
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPush || operation == UINavigationControllerOperationPop)
{
self.animator = [Animator new];
return self.animator;
}
return nil;
}
animator.m
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha = 0;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.transform = CGAffineTransformMakeScale(0.1, 0.1);
toViewController.view.alpha = 1;
} completion:^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
After the pushPage and the screen appears, there is a problem : are visible all the elements that hide in the code and I can see how elements disappear already seeing the screen. It looks unsightly . There is a way ?
inside animateTransition (id)transitionContext protocol push and pop transition should has to be handled separately as below.
//1.Settings for the fromVC ..
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
CGRect sourceRect = [transitionContext initialFrameForViewController:fromVC];
CGRect finalFrameForVC = [transitionContext finalFrameForViewController:toVC];
//2.Insert the toVC view.
if(pushCondition) {
UIView *container = [transitionContext containerView];
[container insertSubview:toVC.view aboveSubview:fromVC.view];
toVC.view.alpha = 0.5;
toVC.view =
} else if (popCondition ){
UIView *container = [transitionContext containerView];
toVC.view.frame = finalFrameForVC;
toVC.view.alpha = 0.5;
[container addSubview:toVC.view];
[container sendSubviewToBack:toVC.view];
UIView *snapShoot = [fromVC.view snapshotViewAfterScreenUpdates:false];
}
//3.Perform the animation.
[UIView animateWithDuration:1.0
delay:0.0
usingSpringWithDamping:1.0
initialSpringVelocity:6.0
options:UIViewAnimationOptionCurveLinear
animations:^{
//Setup the final parameters of views for push
toVC.view // update final view frame
toVC.view.alpha = 1.0;
//Setup the final parameters of views for pop
snapShoot.frame =
} completion:^(BOOL finished) {
//When the animation is completed call completeTransition with final push value
[snapShoot removeFromSuperview];
//When the animation is completed call completeTransition with final push value
toVC.view.alpha= 1.0;
[transitionContext completeTransition:YES];
}];
Create enum / flag property inside amimator and set it inside navigation controller delegate.
if (operation == UINavigationControllerOperationPush)
{
self.animator = [Animator new];
self.animator.pushPopAnimation = UINavigationControllerOperationPush;
return self.animator;
}
See the apple documentation of snapshotViewAfterScreenUpdates:(BOOL)afterUpdates method in code above.
I would like to custom push and pop a view controller use pull down/up animation like this:
I try to change the y position but it doesn't work (it doesn't show up animation at all).
[self.navigationController pushViewController:nextController animated:NO];
self.view.frame = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y, self.view.frame.size.width, self.view.frame.size.height);
[UIView animateWithDuration:0.6 animations:^{
self.view.frame = CGRectMake(0, -self.view.frame.size.height, self.view.frame.size.width, self.view.frame.size.height);
} completion:nil];
Is there any suggestion?
P/s: I have to use push view controller in this case instead of presentViewController
Update:
I try to use UINavigationControllerDelegate like this:
PropertyViewController.h
#interface PropertyViewController : UIViewController <UINavigationControllerDelegate>{
}
PropertyViewController.m
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
NSLog(#"willShowViewController");
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
NSLog(#"didShowViewController");
}
-(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
// THIS METHOD IS NOT CALLED AT ALL
NSLog(#"animationControllerForOperation");
TLTransitionAnimator *animator = [TLTransitionAnimator new];
animator.presenting = (operation == UINavigationControllerOperationPush);
animator.duration = 0.5;
return animator;
}
- (void)viewGallery{
// PUSH VIEW CONTROLLER
GalleryViewController* galleryController = [[GalleryViewController alloc] initWithNibName:#"GalleryViewController" bundle:nil];
galleryController.navigationController.delegate = self;
[self.navigationController pushViewController:galleryController animated:YES];
}
Update 2:
After fix the problem method not called with this line in PropertyViewController.m
self.navigationController.delegate = self;
I face with another problem.
The slide down animation on push does work, but the slide up doesn't .Here is my custom animation:
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
if (self.presenting) { // push
fromViewController.view.transform = CGAffineTransformIdentity;
toViewController.view.transform = CGAffineTransformMakeTranslation(0, toViewController.view.frame.size.height);
}else{ // pop
fromViewController.view.transform = CGAffineTransformMakeTranslation(0, fromViewController.view.frame.size.height);
}
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
if (self.presenting) { // push
fromViewController.view.transform = CGAffineTransformMakeTranslation(0, toViewController.view.frame.size.height);
toViewController.view.transform = CGAffineTransformMakeTranslation(0, toViewController.view.frame.size.height);
} else { // pop
fromViewController.view.transform = CGAffineTransformMakeTranslation(0, 0);
}
} completion:^(BOOL finished) {
toViewController.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
The problem 2:
The pull down animation only works on the first time, from the second times, layout renders wrong, I have no idea why this can happen.
Where am I wrong here?
You should Implement UIViewControllerAnimatedTransitioning.
Make your FromViewController.m conform to UINavigationControllerDelegate. Other sample code out there tells you to conform to UIViewControllerTransitioningDelegate, but that's only if you're presenting the ToViewController.
#interface ViewController : UIViewController <UINavigationControllerDelegate>
Return your custom transition animator object in the delegate callback method in FromViewController:
- (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC {
TransitionAnimator *animator = [TransitionAnimator new];
animator.presenting = (operation == UINavigationControllerOperationPush);
return animator;
}
Create your custom TransitionAnimator animator class and paste these sample methods:
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
return 0.5f;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
if (self.presenting) {
toViewController.view.transform = CGAffineTransformMakeTranslation(0, toViewController.view.frame.size.height);
}
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
if (self.presenting) {
toViewController.view.transform = CGAffineTransformIdentity;
} else {
fromViewController.view.transform = CGAffineTransformMakeTranslation(0, toViewController.view.frame.size.height);
}
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
Most of the implementation is from here, I just edited the transition method to your needs: https://stackoverflow.com/a/25026102/2242359
I still strongly recommend to go over some or all these guides:
http://www.teehanlax.com/blog/custom-uiviewcontroller-transitions/
http://www.objc.io/issue-5/view-controller-transitions.html
http://objectivetoast.com/2014/03/17/custom-transitions-on-ios/
Try this :
UIViewController *yourViewController = [[UIViewController alloc]init];
[UIView beginAnimations: #"Showinfo"context: nil];
[UIView setAnimationCurve: UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[self.navigationController pushViewController: yourViewController animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
i'm trying to present PopUp ViewController with animation, using UIViewcontrollerAnimatedTransitioning. I've created a Modal Segue from TableViewCell to my Viewcontroller
In PopupPresentAnimationController (that implement UIViewcontrollerAnimatedTransitioning) i have
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[fromViewController addChildViewController:toViewController];
toViewController.view.frame = fromViewController.view.frame;
[fromViewController.view addSubview:toViewController.view];
[toViewController didMoveToParentViewController:fromViewController];
NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration delay:0.0 usingSpringWithDamping:0.6 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
fromViewController.view.alpha = 0.5;
} completion:^(BOOL finished) {
fromViewController.view.alpha = 1.0;
[transitionContext completeTransition:YES];
}];
}
The PopUpViewController have a background black with 50% opacity and, when it appear all "works" but after animation end, the screen become black.
UPDATE 1:
UIViewController *fromViewController =[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *container = [transitionContext containerView];
CGRect initialFrame = toViewController.view.frame;
initialFrame.origin.y = toViewController.view.frame.size.height;
toViewController.view.frame = initialFrame;
[container insertSubview:toViewController.view aboveSubview:fromViewController.view];
NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration delay:0 options:0 animations:^{
CGRect newFrame = toViewController.view.frame;
newFrame.origin.y = 0;
toViewController.view.frame = newFrame;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
UPDATE 2
In prepareForSegue i have added the follow line
controller.modalPresentationStyle = UIModalPresentationCustom; // controller is the destination
With this line on, the black is no more black!
The problem you're having is that you are adding your toViewController to the fromViewController. When the animation ends, the toViewController is removed and in turn, so is your fromViewController. The proper way to handle this is to use the container view provided by the context:
UIView *container = [transitionContext containerView];
This is an example of how to do a popup transition:
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *container = [transitionContext containerView];
if (!self.beingDismissed) {
//Make controller hidden so it can slide in
CGRect initialFrame = toViewController.view.frame;
initialFrame.origin.y = toViewController.view.frame.size.height;
toViewController.view.frame = initialFrame;
[container insertSubview:toViewController.view aboveSubview:fromViewController.view];
}
[UIView animateKeyframesWithDuration:0.5 delay:0 options:0 animations:^{
if (!self.beingDismissed) {
//Show view controller
CGRect newFrame = toViewController.view.frame;
newFrame.origin.y = 0;
toViewController.view.frame = newFrame;
} else {
//Hide view controller
CGRect newFrame = fromViewController.view.frame;
newFrame.origin.y = fromViewController.view.frame.size.height;
fromViewController.view.frame = newFrame;
}
} completion:^(BOOL finished) {
[transitionContext completeTransition:finished];
}];
}
I am hoping you can help me I am trying to implement a Custom Transition in my app that will display a Custom Modal View Controller. I am able to get the View Controller to appear as expected, however, I cannot get the transition to animate. Any help with this or pointers in the right direction would be greatly appreciated.
Please see my code below:
-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
return 1.0;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
CGRect sourceRect = [transitionContext initialFrameForViewController:fromVC];
fromVC.view.frame = sourceRect;
UIGraphicsBeginImageContextWithOptions(fromVC.view.frame.size, NO, 0);
[fromVC.view drawViewHierarchyInRect:fromVC.view.bounds afterScreenUpdates:YES];
UIImage *fromVCImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIView *container = [transitionContext containerView];
[container setBackgroundColor:[UIColor colorWithPatternImage:fromVCImage]];
[container insertSubview:toVC.view belowSubview:fromVC.view];
CGRect fromVCFrame = fromVC.view.frame;
__block CGRect toVCFrame = toVC.view.frame;
toVCFrame.origin.y = fromVCFrame.origin.y + fromVCFrame.size.height;
[toVC.view setFrame:toVCFrame];
[toVC.view setAlpha:0.0];
[toVC.view setBackgroundColor:[UIColor colorWithPatternImage:fromVCImage]];
[transitionContext finalFrameForViewController:toVC];
//3.Perform the animation...............................
[UIView animateWithDuration:1.0
animations:^{
toVCFrame.origin.y = fromVCFrame.origin.y;
toVC.view.frame = toVCFrame;
[toVC.view setAlpha:1.0];
}
completion:^(BOOL finished) {
//When the animation is completed call completeTransition
[transitionContext completeTransition:YES];
}];
}
Adding Code from View Controller when presentViewController is called via a UITapGestureRecognizer
- (void) onTapGesture:(UITapGestureRecognizer *)recognizer
{
ImagesCollectionViewController *imagesCollectionView = [[Utils getStoryboardForDeviceWithIdentifier:MAIN_STORYBOARD] instantiateViewControllerWithIdentifier:IMAGES_VIEW];
imagesCollectionView.transitioningDelegate = self;
imagesCollectionView.modalTransitionStyle = UIModalPresentationCustom;
[self presentViewController:imagesCollectionView animated:YES completion:^{
}];
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
return self.contentSlideTransitionManager;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
return self.contentSlideTransitionManager;
}
I'm going to guess that this is your problem:
[container insertSubview:toVC.view belowSubview:fromVC.view];
If toVC.view is below fromVC.view, it is hidden behind it, so you are not seeing what it does.