88

I am using presentViewController to present new screen

let dashboardWorkout = DashboardWorkoutViewController()
presentViewController(dashboardWorkout, animated: true, completion: nil)

This presents new screen from bottom to top but I want it to presented from right to left without using UINavigationController.

I am using Xib instead of storyboard so how can I do that ?

AnthoPak
  • 3,531
  • 3
  • 18
  • 34
Umair Afzal
  • 4,398
  • 5
  • 22
  • 46

13 Answers13

166

It doesn't matter if it is xib or storyboard that you are using. Normally, the right to left transition is used when you push a view controller into presentor's UINavigiationController.

UPDATE

Added timing function kCAMediaTimingFunctionEaseInEaseOut

Sample project with Swift 4 implementation added to GitHub


Swift 3 & 4.2

let transition = CATransition()
transition.duration = 0.5
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromRight
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
view.window!.layer.add(transition, forKey: kCATransition)
present(dashboardWorkout, animated: false, completion: nil)


ObjC

CATransition *transition = [[CATransition alloc] init];
transition.duration = 0.5;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
[transition setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[self.view.window.layer addAnimation:transition forKey:kCATransition];
[self presentViewController:dashboardWorkout animated:false completion:nil];


Swift 2.x

let transition = CATransition()
transition.duration = 0.5
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromRight
transition.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
view.window!.layer.addAnimation(transition, forKey: kCATransition)
presentViewController(dashboardWorkout, animated: false, completion: nil)

Seems like the animated parameter in the presentViewController method doesn't really matter in this case of custom transition. It can be of any value, either true or false.

tonymontana
  • 4,523
  • 4
  • 26
  • 48
  • 2
    I tried to use Swift3 code within perform() method of a custom UIStoryboardSegue, problem is - the transition is done on the same view, and then, the second view appear. Any ideas about that? – Devarshi Oct 27 '16 at 11:48
  • 2
    uUrrently when I use this method, source VC is fading out How can i remove that fade effect and have its just like the push animation ? – Umair Afzal Apr 28 '17 at 04:33
  • 1
    @UmairAfzal Thats color of window, You need to change color of window to avoid that. – Abdul Rehman Jun 02 '17 at 17:07
  • How to use this transition effectively with UIModalPresentationStyle.overCurrentContext in Swift 3 – Mamta Dec 09 '17 at 15:07
  • For anyone googling here, kCATransitionMoveIn is often what you want, also. – Fattie Jan 02 '18 at 16:26
  • 4
    If you se the "animated" to "true" it will have a slight upward animation too. I recommend setting it to "false" – Rebeloper May 18 '18 at 08:05
78

Complete code for present/dismiss, Swift 3

extension UIViewController {

    func presentDetail(_ viewControllerToPresent: UIViewController) {
        let transition = CATransition()
        transition.duration = 0.25
        transition.type = kCATransitionPush
        transition.subtype = kCATransitionFromRight
        self.view.window!.layer.add(transition, forKey: kCATransition)

        present(viewControllerToPresent, animated: false)
    }

    func dismissDetail() {
        let transition = CATransition()
        transition.duration = 0.25
        transition.type = kCATransitionPush
        transition.subtype = kCATransitionFromLeft
        self.view.window!.layer.add(transition, forKey: kCATransition)

        dismiss(animated: false)
    }
}
49

Read up all answers and can't see correct solution. The right way do to so is to make custom UIViewControllerAnimatedTransitioning for presented VC delegate.

So it assumes to make more steps, but the result is more customizable and haven't some side effects, like moving from view together with presented view.

So, assume you have some ViewController, and there is a method for presenting

var presentTransition: UIViewControllerAnimatedTransitioning?
var dismissTransition: UIViewControllerAnimatedTransitioning?    

func showSettings(animated: Bool) {
    let vc = ... create new vc to present

    presentTransition = RightToLeftTransition()
    dismissTransition = LeftToRightTransition()

    vc.modalPresentationStyle = .custom
    vc.transitioningDelegate = self

    present(vc, animated: true, completion: { [weak self] in
        self?.presentTransition = nil
    })
}

presentTransition and dismissTransition is used for animating your view controllers. So you adopt your ViewController to UIViewControllerTransitioningDelegate:

extension ViewController: UIViewControllerTransitioningDelegate {
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return presentTransition
    }

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return dismissTransition
    }
}

So the last step is to create your custom transition:

class RightToLeftTransition: NSObject, UIViewControllerAnimatedTransitioning {
    let duration: TimeInterval = 0.25

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return duration
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let container = transitionContext.containerView
        let toView = transitionContext.view(forKey: .to)!

        container.addSubview(toView)
        toView.frame.origin = CGPoint(x: toView.frame.width, y: 0)

        UIView.animate(withDuration: duration, delay: 0, options: .curveEaseOut, animations: {
            toView.frame.origin = CGPoint(x: 0, y: 0)
        }, completion: { _ in
            transitionContext.completeTransition(true)
        })
    }
}

class LeftToRightTransition: NSObject, UIViewControllerAnimatedTransitioning {
    let duration: TimeInterval = 0.25

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return duration
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let container = transitionContext.containerView
        let fromView = transitionContext.view(forKey: .from)!

        container.addSubview(fromView)
        fromView.frame.origin = .zero

        UIView.animate(withDuration: duration, delay: 0, options: .curveEaseIn, animations: {
            fromView.frame.origin = CGPoint(x: fromView.frame.width, y: 0)
        }, completion: { _ in
            fromView.removeFromSuperview()
            transitionContext.completeTransition(true)
        })
    }
}

In that code view controller is presented over current context, you can make your customizations from that point. Also you may see custom UIPresentationController is useful as well (pass in using UIViewControllerTransitioningDelegate)

HotJard
  • 3,910
  • 2
  • 31
  • 31
  • Well Done! I was trying all the answers for a while and there was something not right... I even upvoted One that should have not been... – Reimond Hill May 11 '18 at 14:03
  • when do you dismiss the presented VC, do you just call dismiss(animated:true,completion:nil) ??? – Reimond Hill May 11 '18 at 14:06
  • @ReimondHill yes, I'm just using ordinary dismissViewController – HotJard May 11 '18 at 16:53
  • This is the by far the best solution. Yes, it's a little more complicated, but it's robust and won't break under various conditions. The accepted answer is a hack. – mmh02 Oct 24 '18 at 20:53
  • Its working perfectly on all devices except, iPhone 7+, 8+, X, XMax. Any idea why ? the vc is not taking the full frame. – Umair Afzal Jan 12 '19 at 15:52
  • @UmairAfzal 1) issue with constraints (in IB f.e. iPhone 7 and testing in X) or most likely 2) frame is calculated wrong due to safe area - try to replace in RightToLeftTransition and LeftToRightTransition "y" to safe area height ()https://developer.apple.com/documentation/uikit/uiview/positioning_content_relative_to_the_safe_area) , the size of view may be also changed. Actually I don't have this code right now to check, so if you fix it please provide the code and I'll edit the answer – HotJard Jan 14 '19 at 07:45
  • This is the best solution and the only one working properly for me. Should be marked as accepted – Mateusz May 11 '19 at 10:00
  • This is simply the only correct way, indeed. My long post about it ! https://stackoverflow.com/a/48081504/294884 – Fattie Jun 24 '19 at 13:30
  • Best solution because it will avoid flash effect transition if you using CATransition instance. – duongvanthai Sep 07 '19 at 16:25
  • I faced one issue on iPhone 7, iOS 13.3: width of view was wider than container. But ``` toView.frame = container.frame``` fixed problem. – Александр Грабовский Mar 26 '20 at 04:55
15

You can also use custom segue.

Swift 5

class SegueFromRight: UIStoryboardSegue {

    override func perform() {
        let src = self.source
        let dst = self.destination

        src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
        dst.view.transform = CGAffineTransform(translationX: src.view.frame.size.width, y: 0)

        UIView.animate(withDuration: 0.25,
               delay: 0.0,
               options: UIView.AnimationOptions.curveEaseInOut,
               animations: {
                    dst.view.transform = CGAffineTransform(translationX: 0, y: 0)
            },
                   completion: { finished in
                    src.present(dst, animated: false, completion: nil)
        })
    }
}
Jalil
  • 1,025
  • 11
  • 29
Photon Point
  • 749
  • 2
  • 13
  • 23
7

Try this,

    let animation = CATransition()
    animation.duration = 0.5
    animation.type = kCATransitionPush
    animation.subtype = kCATransitionFromRight
     animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    vc.view.layer.addAnimation(animation, forKey: "SwitchToView")

    self.presentViewController(vc, animated: false, completion: nil)

Here vc is viewcontroller, dashboardWorkout in your case.

Ketan Parmar
  • 25,426
  • 9
  • 43
  • 67
  • 3
    Thanks, but next VC is appearin all of sudden instead of apperaing from right to left. I changed animation duration but still same – Umair Afzal Jun 09 '16 at 10:07
5

import UIKit and create one extension for UIViewController:

extension UIViewController {
func transitionVc(vc: UIViewController, duration: CFTimeInterval, type: CATransitionSubtype) {
    let customVcTransition = vc
    let transition = CATransition()
    transition.duration = duration
    transition.type = CATransitionType.push
    transition.subtype = type
    transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
    view.window!.layer.add(transition, forKey: kCATransition)
    present(customVcTransition, animated: false, completion: nil)
}}

after simlpy call:

let vC = YourViewController()
transitionVc(vc: vC, duration: 0.5, type: .fromRight)

from left to right:

let vC = YourViewController()
transitionVc(vc: vC, duration: 0.5, type: .fromleft)

you can change the duration with your preferred duration...

Fabio
  • 3,653
  • 3
  • 16
  • 17
4

If you do want to use the "quick fix" CATransition method....

class AA: UIViewController

 func goToBB {

    let bb = .. instantiateViewcontroller, storyboard etc .. as! AlreadyOnboardLogin

    let tr = CATransition()
    tr.duration = 0.25
    tr.type = kCATransitionMoveIn // use "MoveIn" here
    tr.subtype = kCATransitionFromRight
    view.window!.layer.add(tr, forKey: kCATransition)

    present(bb, animated: false)
    bb.delegate, etc = set any other needed values
}

and then ...

func dismissingBB() {

    let tr = CATransition()
    tr.duration = 0.25
    tr.type = kCATransitionReveal // use "Reveal" here
    tr.subtype = kCATransitionFromLeft
    view.window!.layer.add(tr, forKey: kCATransition)

    dismiss(self) .. or dismiss(bb), or whatever
}

All of this is unfortunately not really correct :(

CATransition is not really made for doing this job.

Note you will get the annoying cross fade to black which unfortunately ruins the effect.


Many devs (like me) really don't like using NavigationController. Often, it is more flexible to just present in an ad-hoc manner as you go, particularly for unusual and complex apps. However, it's not difficult to "add" a nav controller.

  1. simply on storyboard, go to the entry VC and click "embed -> in nav controller". Really that's it. Or,

  2. in didFinishLaunchingWithOptions it's easy to build your nav controller if you prefer

  3. you really don't even need to keep the variable anywhere, as .navigationController is always available as a property - easy.

Really, once you have a navigationController, it's trivial to do transitions between screens,

    let nextScreen = instantiateViewController etc as! NextScreen
    navigationController?
        .pushViewController(nextScreen, animated: true)

and you can pop.

That however only gives you the standard apple "dual push" effect...

(The old one slides off at a lower speed, as the new one slides on.)

Generally and surprisingly, you usually have to make the effort to do a full custom transition.

Even if you just want the simplest, most common, move-over/move-off transition, you do have to do a full custom transition.

Fortunately, to do that there's some cut and paste boilerplate code on this QA... https://stackoverflow.com/a/48081504/294884 . Happy new year 2018!

Fattie
  • 30,632
  • 54
  • 336
  • 607
1

Try this.

let transition: CATransition = CATransition()
transition.duration = 0.3

transition.type = kCATransitionReveal
transition.subtype = kCATransitionFromLeft
self.view.window!.layer.addAnimation(transition, forKey: nil)
self.dismissViewControllerAnimated(false, completion: nil)
Pang
  • 8,605
  • 144
  • 77
  • 113
Ashwin Felix
  • 205
  • 2
  • 6
1
let transition = CATransition()
    transition.duration = 0.25
    transition.type = kCATransitionPush
    transition.subtype = kCATransitionFromLeft
    transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
    tabBarController?.view.layer.add(transition, forKey: kCATransition)
    self.navigationController?.popToRootViewController(animated: true)
Chilli
  • 11
  • 2
  • I added this in my button clicked event for animation. It works for me in Xcode 10.2 swift 4. – Chilli Jan 25 '19 at 09:54
0

present view controller from right to left in iOS using Swift

func FrkTransition() 

{
    let transition = CATransition()

    transition.duration = 2

    transition.type = kCATransitionPush

    transitioningLayer.add(transition,forKey: "transition")

    // Transition to "blue" state

    transitioningLayer.backgroundColor = UIColor.blue.cgColor
    transitioningLayer.string = "Blue"
}

Refrence By : [https://developer.apple.com/documentation/quartzcore/catransition][1]

0
    // Never Easy than this before :)
     // you just need to add a Static function for navigation transition
     // This Code is recommended for the view controller.
  
     public class CustomNavigation:  UIViewController {
            
            
            public override func loadView() {
                super.loadView();
                
            }
            public override func viewDidLoad() {
                super.viewDidLoad()
                // Do any additional setup after loading the view.
            }
            
            public override func viewDidAppear(_ animated: Bool) {
                super.viewDidAppear(true);
            }
        
        public static func segueNavToLeft(view: UIView) {
                let transition = CATransition()
                transition.duration = 0.3
                transition.type = CATransitionType.push
                transition.subtype = CATransitionSubtype.fromLeft
                transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
                view.window!.layer.add(transition, forKey: kCATransition)
            }
            
            public static func segueNavToRight(view: UIView) {
                let transition = CATransition()
                transition.duration = 0.3
                transition.type = CATransitionType.push
                transition.subtype = CATransitionSubtype.fromRight
                transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
                view.window!.layer.add(transition, forKey: kCATransition)
            }
            
        }
    
    
    
    // simply call in your viewcontroller:
     func moveToRight()  {
            
            CustomNavigation.segueNavToRight(view: view)
    
            let controller = self.storyboard?.instantiateViewController(withIdentifier: "id") as! YourViewController
            let navigationController = UINavigationController(rootViewController: YourViewController)
            navigationController.modalPresentationStyle = .fullScreen
            self.present(navigationController, animated: false, completion: nil)
            
          
        }

func moveToLeft() {

       CustomNavigation.segueNavToLeft(view: view)
        self.dismiss(animated: true, completion: nil)
} 
-1

I have a better solution that has worked for me if you want to mantain the classic animation of Apple, without see that "black" transition that you see using CATransition(). Simply use popToViewController method.

You can look for the viewController you need in

self.navigationController.viewControllers // Array of VC that contains all VC of current stack

You can look for the viewController you need by searching for it's restorationIdentifier.

BE CAREFUL: for example, if you are navigating through 3 view controllers, and when arrive to the last you want to pop the first. You will lose, in this case, the refer to the effective SECOND view controller because the popToViewController has overwritten it. BTW, there's a solution: you can easily save before the popToViewcontroller, the VC you will need later. It worked for me very well.

Edoardo Vicoli
  • 167
  • 1
  • 7
-4

This worked for me :

self.navigationController?.pushViewController(controller, animated: true)
Mathis Delaunay
  • 154
  • 1
  • 15