Currently I am using [self presentModalViewController :newVC animated:YES]
.I want to present newViewcontroller from left/right/top/bottom with a push effect. I tried to use CATransition but it displays a black screen in between the transition.

- 10,517
- 10
- 58
- 71

- 507
- 1
- 5
- 9
-
For other people with this problem, I posted the solution I found here: http://stackoverflow.com/questions/6876292/iphone-presentmodalviewcontroller-with-transition-from-right/10842991#10842991 – Agustin Estevez Jun 01 '12 at 00:34
6 Answers
When present:
CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
[self.view.window.layer addAnimation:transition forKey:nil];
[self presentModalViewController:viewCtrl animated:NO];
When dismiss:
CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
[self.view.window.layer addAnimation:transition forKey:nil];
[self dismissModalViewControllerAnimated:NO];
-
5This seems to be no longer working on iOS 8, as the presenting VC is moving instead of the presented one. Any idea how to fix it? – Maiaux Sep 22 '14 at 23:07
-
1
-
-
-
-
2@wcrane I am also getting black screen flashing. Actually fadein fadeout and fadein animation happening. Any hints i am trying to remove the black shade – karthikPrabhu Alagu Jun 05 '16 at 13:33
After X >= 4 hours of work, this works without "tearing" or other background artifacts:
class AboutTransition: NSObject, UIViewControllerAnimatedTransitioning {
let presenting: Bool
let duration: NSTimeInterval
init(presenting: Bool, duration: NSTimeInterval = 0.25) {
self.presenting = presenting
self.duration = duration
}
@objc func transitionDuration(ctx: UIViewControllerContextTransitioning) -> NSTimeInterval {
return duration
}
@objc func animateTransition(ctx: UIViewControllerContextTransitioning) {
let duration = transitionDuration(ctx)
let containerView = ctx.containerView()
let fromViewController = ctx.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toViewController = ctx.viewControllerForKey(UITransitionContextToViewControllerKey)!
let fromView = fromViewController.view // 7.0 & 8.0 compatible vs. viewForKey:
let toView = toViewController.view // 7.0 & 8.0 compatible vs. viewForKey:
containerView.addSubview(fromView)
containerView.addSubview(toView)
let offScreenRight = CGAffineTransformMakeTranslation(containerView.frame.width, 0)
let offScreenLeft = CGAffineTransformMakeTranslation(-containerView.frame.width, 0)
fromView.transform = CGAffineTransformIdentity
toView.transform = self.presenting ? offScreenRight : offScreenLeft
UIView.animateWithDuration(duration, delay:0, options:UIViewAnimationOptions(0), animations: {
fromView.transform = self.presenting ? offScreenLeft : offScreenRight
toView.transform = CGAffineTransformIdentity
}, completion: { (finished: Bool) in
ctx.completeTransition(finished)
if finished {
fromView.removeFromSuperview()
fromView.frame = toView.frame // reset the frame for reuse, otherwise frame transforms will accumulate
}
})
}
}
Then in AppDelegate.swift
:
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDelegate {
lazy var window: UIWindow? = {
return UIWindow(frame: UIScreen.mainScreen().bounds)
}()
lazy var storyboard = UIStoryboard(name: "Main", bundle: nil)
lazy var nav: UINavigationController = {
var r = self.storyboard.instantiateInitialViewController() as! UINavigationController
r.delegate = self
return r
}()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.window!.rootViewController = nav
self.window!.makeKeyAndVisible()
// .. other setup
return true
}
// ...
// MARK: - UINavigationControllerDelegate
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
// Example of a SettingsViewController which pushes AboutViewController
if fromVC is SettingsViewController && toVC is AboutViewController { // Push to About
return AboutTransition(presenting: true)
} else if fromVC is AboutViewController && toVC is SettingsViewController { // Pop to Settings
return AboutTransition(presenting: false)
}
return nil
}
}
This assumes an app is using the default storyboard with an initial VC as a UINavigationController
Update for swift 3/4
class LTRTransition: NSObject, UIViewControllerAnimatedTransitioning
{
let presenting: Bool
let duration: TimeInterval
init(presenting: Bool, duration: TimeInterval = Theme.currentTheme.animationDuration) {
self.presenting = presenting
self.duration = duration
}
@objc func transitionDuration(using ctx: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
@objc func animateTransition(using ctx: UIViewControllerContextTransitioning) {
let duration = transitionDuration(using: ctx)
let containerView = ctx.containerView
let fromViewController = ctx.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toViewController = ctx.viewController(forKey: UITransitionContextViewControllerKey.to)!
guard let fromView = fromViewController.view, // 7.0 & 8.0 compatible vs. viewForKey:
let toView = toViewController.view // 7.0 & 8.0 compatible vs. viewForKey:
else {
assertionFailure("fix this")
return
}
containerView.addSubview(fromView)
containerView.addSubview(toView)
let offScreenRight = CGAffineTransform(translationX: containerView.frame.width, y: 0)
let offScreenLeft = CGAffineTransform(translationX: -containerView.frame.width, y: 0)
fromView.transform = CGAffineTransform.identity
toView.transform = self.presenting ? offScreenRight : offScreenLeft
UIView.animate(withDuration: duration, delay:0, options:UIViewAnimationOptions(rawValue: 0), animations: {
fromView.transform = self.presenting ? offScreenLeft : offScreenRight
toView.transform = CGAffineTransform.identity
}, completion: { (finished: Bool) in
ctx.completeTransition(finished)
if finished {
fromView.removeFromSuperview()
// this screws up dismissal. at least on ios10 it does fromView.frame = toView.frame // reset the frame for reuse, otherwise frame transforms will accumulate
}
})
}
}
And the transitioniningDelegate implementation for the VC being pushed/dismissed:
extension WhateverVCyouArePresentingFrom: UIViewControllerTransitioningDelegate
{
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
return LTRTransition(presenting: true)
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
return LTRTransition(presenting: false)
}
}

- 5,486
- 5
- 41
- 66
It looks tricky, but make it done with just several lines of code.
First, Present LeftSlideViewController modally. You need to specify modalPresentationStyle to .overCurrentContext.
if let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LeftSlideViewController") as? LeftSlideViewController {
vc.modalPresentationStyle = .overCurrentContext
self.present(vc, animated: false, completion: nil)
}
And then, Open LeftSlideViewController.
To move-in from left (Using CATransition)
Hide a view in loadView.
self.view.isHidden = true
Add left-move-in transition animation to a view in viewDidAppear
self.view.isHidden = false let transition = CATransition.init() transition.duration = 0.3 transition.timingFunction = CAMediaTimingFunction.init(name: .easeInEaseOut) transition.type = .moveIn transition.subtype = .fromLeft self.view.layer.add(transition, forKey: nil)
To move-out to left (Using UIView animation)
Animate the view's frame by using UIView's animation and dismiss ViewController when the animation finished
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn, animations: { self.view.frame = CGRect(x: self.view.frame.width * -1, y: 0, width: self.view.frame.width, height: self.view.frame.height) }) { (finished) in self.dismiss(animated: false, completion: nil) }

- 56
- 2
I had the same problem. Say you want to present a view controller 2 from view controller 1. In the first view controller use
[self presentModalViewController: controller animated: NO]];
In the second view controller, in viewWillAppear: method add the code
CATransition *animation = [CATransition animation];
[animation setDelegate:self];
[animation setType:kCATransitionPush];
[animation setSubtype:kCATransitionFromRight];
[animation setDuration:0.40];
[animation setTimingFunction:
[CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionEaseInEaseOut]];
[self.view.layer addAnimation:animation forKey:kCATransition];
It will work fine. If black screen comes again, if you are using navigation controller, replace
[self.view.layer addAnimation:animation forKey:kCATransition];
with
[self.navigationController.view.layer addAnimation:animation forKey:kCATransition];

- 4,227
- 4
- 28
- 42
-
I also have the black screen problem. Before the modal controller is pushed in a black screen appears. Anyone solved the problem? – bolonn Feb 26 '13 at 15:56
There are only four UIModalTransitionStyles:
UIModalTransitionStyleCoverVertical
UIModalTransitionStyleFlipHorizontal
UIModalTransitionStyleCrossDissolve
UIModalTransitionStylePartialCurl
For example:
UIViewController *controller = [[[MyViewController alloc] init] autorelease];
UIModalTransitionStyle trans = UIModalTransitionStyleFlipHorizontal;
[UIView beginAnimations: nil context: nil];
[UIView setAnimationTransition: trans forView: [self window] cache: YES];
[navController presentModalViewController: controller animated: NO];
[UIView commitAnimations];

- 12,961
- 41
- 132
- 214
You can set a transitioningDelegate
on the view controller that you want to present. You must conform to UIViewControllerTransitioningDelegate
and UIViewControllerAnimatedTransitioning
protocols. And by implementing animateTransition(transitionContext: UIViewControllerContextTransitioning)
you can animate either view controllers subviews.
Function that performs transition
class fromViewController : UIViewController {
let transitionManager = TransitionManager()
func transitionToViewController() {
toController.transitioningDelegate = transitionManager
presentViewController(toController, animated: true, completion: nil)
}
}
Then the TransitionManager looks like:
import UIKit
class TransitionManager : UIPercentDrivenInteractiveTransition {
var presenting = false
}
// MARK: UIViewControllerAnimatedTransitioning
extension TransitionManager : UIViewControllerAnimatedTransitioning {
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView()
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
let offScreenRight = CGAffineTransformMakeTranslation(container.frame.width, 0)
let offScreenLeft = CGAffineTransformMakeTranslation(-container.frame.width, 0)
toView.transform = self.presenting ? offScreenRight : offScreenLeft
container.addSubview(toView)
container.addSubview(fromView)
let duration = self.transitionDuration(transitionContext)
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0.8, options: nil, animations: {
fromView.transform = self.presenting ? offScreenLeft : offScreenRight
toView.transform = CGAffineTransformIdentity
}, completion: { finished in
transitionContext.completeTransition(true)
})
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 0.3
}
}
// MARK: UIViewControllerTransitioningDelegate
extension TransitionManager : UIViewControllerTransitioningDelegate {
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.presenting = true
return self
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.presenting = false
return self
}
}

- 1,461
- 14
- 8
-
Doesn't work. The `self.presenting == false` case results in a black screen with the wrong frame – Jun 12 '15 at 04:22
-
@Barry This works for me not using segues rather manual transitions between controllers in different storyboards.. By the looks at the code you posted above you are trying to override a push transition of a UINavigationController. Did you have the segue in storyboard set to push rather than present? – Nick Wargnier Jun 13 '15 at 01:21