I am trying to create a push segue animation where the sourceVC drops down off screen vertically and the destinationVC appears upwards onto the screen vertically. My custom segue seems to almost make this animation, however, upon the destinationVC appearing the animation does not work. Here is my -perform segue.
-(void)perform
{
UIViewController *sourceVC = self.sourceViewController;
UIViewController *destVC = self.destinationViewController;
[sourceVC.view addSubview:destVC.view];
float width = sourceVC.view.frame.size.width;
float height = sourceVC.view.frame.size.height;
destVC.view.frame = CGRectMake(0, 560, width, height);
[UIView animateWithDuration:.2 delay:0 options:UIViewAnimationOptionCurveLinear
animations:^{
[destVC.view removeFromSuperview];
sourceVC.view.frame = CGRectMake(0, 560, width, height);
} completion:^(BOOL finished) {
[UIView animateWithDuration:.2 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
destVC.view.frame = CGRectMake(0, 0, destVC.view.frame.size.width, destVC.view.frame.size.height);
} completion:^(BOOL finished) {
[sourceVC.navigationController pushViewController:destVC animated:NO];
}];
}];
}
The second animation is the one that is not working.
Any ideas what is wrong with my code? Thanks for any help.
I needed to create a custom Animator class that conformed to UIViewControllerAnimatedTransitioning.
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface SAAnimator : NSObject <UIViewControllerAnimatedTransitioning>
#end
I then implemented the required methods.
#implementation SAAnimator
-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.2;
}
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
fromViewController.view.userInteractionEnabled = NO;
[[transitionContext containerView]addSubview:fromViewController.view];
[[transitionContext containerView]addSubview:toViewController.view];
toViewController.view.frame = CGRectMake(0, 560, toViewController.view.frame.size.width, toViewController.view.frame.size.height);
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
fromViewController.view.frame = CGRectMake(0, 560, fromViewController.view.frame.size.width, fromViewController.view.frame.size.height);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
toViewController.view.frame = CGRectMake(0, 0, toViewController.view.frame.size.width, toViewController.view.frame.size.height);
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}];
}
#end
Then very simply, in my view controller performing the push segue, I made the class conform to the UINavigationControllerDelegate and implement this method.
#pragma mark - Animated Transiton
-(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPush) {
SAAnimator * animator = [SAAnimator new];
return animator;
}
return nil;
}
In prepareForSegue set the navigation controller's delegate
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"toCreateVC"]) {
self.navigationController.delegate = self;
}
}
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
I am trying to get UIViewControllerContextTransitioning working.
What I want:
What I would like to have, is presenting modal view controller with custom animation and transparent background.
Wthat I did:
I created animator implementing UIViewControllerTransitioningDelegate,
Set for modal controller:
self.modalPresentationStyle = UIModalPresentationCustom;
self.transitioningDelegate = self;
What I achieved so far,
Modal controller animates presenting and dismissing view correctly, but after dismissing is finished, entire app becomes black. I used xCode tool to pick what's in window hierarchy, and there is nothing. My guess is, that I changed VC's superview when adding to context's container.
Animator
#implementation AlertAnimator
const static CGFloat kAnimationDuration = 1.2;
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
UIViewController *to = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *from = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
if (self.transitionType == ModalAnimatedTransitioningTypePresent) {
[self animatePresentingInContext:transitionContext toVC:to fromVC:from];
} else if (self.transitionType == ModalAnimatedTransitioningTypeDismiss) {
[self animateDismissingInContext:transitionContext toVC:to fromVC:from];
}
}
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
return kAnimationDuration;
}
- (void)animatePresentingInContext:(id<UIViewControllerContextTransitioning>)transitionContext toVC:(UIViewController *)toVC fromVC:(UIViewController *)fromVC {
CGRect fromVCRect = [transitionContext initialFrameForViewController:fromVC];
CGRect toVCRect = fromVCRect;
toVCRect.origin.y = toVCRect.size.height;
toVC.view.frame = toVCRect;
UIView *container = [transitionContext containerView];
[container addSubview:fromVC.view];
[container addSubview:toVC.view];
[UIView animateWithDuration:kAnimationDuration animations:^{
toVC.view.frame = fromVCRect;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
- (void)animateDismissingInContext:(id<UIViewControllerContextTransitioning>)transitionContext toVC:(UIViewController *)toVC fromVC:(UIViewController *)fromVC {
CGRect fromVCRect = [transitionContext initialFrameForViewController:fromVC];
fromVCRect.origin.y = fromVCRect.size.height;
UIView *container = [transitionContext containerView];
[container addSubview:toVC.view];
[container addSubview:fromVC.view];
[UIView animateWithDuration:kAnimationDuration animations:^{
fromVC.view.frame = fromVCRect;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
#end
Code example:
https://www.dropbox.com/s/oaghtgwvga4nxs4/Test.zip?dl=0
Question:
What I am doing wrong and why screen becomes black?
if this is a presenting animator you should not add the fromVC.view as a subview because its already there. This will cause it to bug out. Try it out and tell me what happens. If its a dismissal animator you shouldn't add the presentingViewController to the hierarchy either.
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 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];
}];
}
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];