11

I am using a UISplitViewController, with a MasterViewController and DetailViewController, without UINavigationControllers.

Currenly the segue animation for master->detail, triggered by

performSegueWithIdentifier("showDetail", sender: self)

consists in the DetailViewController showing up from the bottom upwards.

How can I make that animation showing left-wards?

Daniele B
  • 19,801
  • 29
  • 115
  • 173
  • Can you add a screenshot of your storyboard and the code snippet you're using to call `performSegueWithIdentifier("showDetail", sender: self)`? – Islam Nov 26 '15 at 05:36

5 Answers5

11

I've recently needed to have more control of how the segues are being performed, so I made my custom segue classes which all perform the transition in different directions. Here's one of the implementations:

Swift 2.x

override func perform() {

    //credits to http://www.appcoda.com/custom-segue-animations/

    let firstClassView = self.sourceViewController.view
    let secondClassView = self.destinationViewController.view

    let screenWidth = UIScreen.mainScreen().bounds.size.width
    let screenHeight = UIScreen.mainScreen().bounds.size.height

    secondClassView.frame = CGRectMake(screenWidth, 0, screenWidth, screenHeight)

    if let window = UIApplication.sharedApplication().keyWindow {

        window.insertSubview(secondClassView, aboveSubview: firstClassView)

        UIView.animateWithDuration(0.4, animations: { () -> Void in

            firstClassView.frame = CGRectOffset(firstClassView.frame, -screenWidth, 0)
            secondClassView.frame = CGRectOffset(secondClassView.frame, -screenWidth, 0)

            }) {(Finished) -> Void in

                self.sourceViewController.navigationController?.pushViewController(self.destinationViewController, animated: false)

        }

    }

}

This one will have a "right to left" transition. You can modify this function for your needs by simply changing the initial and ending positions of the source and destination view controller.

Also don't forget that you need to mark your segue as "custom segue", and to assign the new class to it.

UPDATE: Added Swift 3 version

Swift 3

override func perform() {

    //credits to http://www.appcoda.com/custom-segue-animations/

    let firstClassView = self.source.view
    let secondClassView = self.destination.view

    let screenWidth = UIScreen.main.bounds.size.width
    let screenHeight = UIScreen.main.bounds.size.height

    secondClassView?.frame = CGRect(x: screenWidth, y: 0, width: screenWidth, height: screenHeight)

    if let window = UIApplication.shared.keyWindow {

        window.insertSubview(secondClassView!, aboveSubview: firstClassView!)

        UIView.animate(withDuration: 0.4, animations: { () -> Void in

            firstClassView?.frame = (firstClassView?.frame.offsetBy(dx: -screenWidth, dy: 0))!
            secondClassView?.frame = (secondClassView?.frame.offsetBy(dx: -screenWidth, dy: 0))!

        }, completion: {(Finished) -> Void in

            self.source.navigationController?.pushViewController(self.destination, animated: false)

        }) 

    }

}
Armin
  • 629
  • 6
  • 23
  • Works for me in Swift 3, just had to make a lot of suggested corrections from the compiler. – MarksCode Apr 13 '17 at 20:48
  • Thanks. I've added the Swift 3 version now as well. Cheers – Armin Apr 14 '17 at 08:37
  • after this change. Go to previews controller not works. `self.dismiss(animated: true, completion: nil)` works for me before – jose920405 Jun 05 '18 at 16:14
  • dismiss removes a modally presented controller – Armin Jun 05 '18 at 19:59
  • @Armin How are you supposed to dismiss the view Controller `self.dismiss(animated: true, completion: nil)` does not seem to work? – bradford gray Jul 19 '18 at 22:17
  • Works as a drop down replacement to "Show" segue - subclass & mark as Custom in IB. However, unwind sequence stops working. What kind of implementation is needed to get back horizontally? – lardois Apr 19 '19 at 22:01
  • This worked great for me in Swift 5, even with a navigation controller. The top navigation bar slides down after the target controller is presented. I just changed the following code to silence the warning about using keyWindow: if let window = UIApplication.shared.windows.first(where: \.isKeyWindow) { – Jose Santos Feb 16 '23 at 18:05
4

Embed your view controllers in UINavigationControllers.

Per the SplitViewController template: enter image description here

On smaller devices it's going to have to use the Master's navigationController.

Furthermore this question has been answered here and here and here

More from the View Controller Programming Guide:

There are two ways to display a view controller onscreen: embed it in a container view controller or present it. Container view controllers provide an app’s primary navigation….

In storyboards it's not that difficult to embed something in a navigation controller. Click on the view controller you want to embed, then Editor->embed in->navigation controller.

Community
  • 1
  • 1
beyowulf
  • 15,101
  • 2
  • 34
  • 40
  • isn't it possible without a UINavigationController? – Daniele B Nov 20 '15 at 13:33
  • 1
    What is the reluctance? If you don't want the navBar, just say self.navigationController.navBarHidden = true in DetailViewController's viewDidLoad – beyowulf Nov 20 '15 at 13:35
  • I just inserted a UINavigationController between the MasterViewController and DetailViewController, but it still shows from the bottom – Daniele B Nov 20 '15 at 13:46
  • Did your change your transition kind from "present modally" to "show" in interface builder ? – Michaël Azevedo Nov 20 '15 at 13:55
  • @MichaëlAzevedo modally is the default transition for showDetail in a splitViewController without navigation controllers. He's troubled because the master view controller is not embedded in a navigation controller so it can't push the detail view controller and we forgot to tell him to do that. – beyowulf Nov 20 '15 at 13:58
  • @SwiftArchitect please provide your answer so I can critique it. – beyowulf Nov 27 '15 at 02:58
  • @SwiftArchitect solved is your word not mine. Moreover I provide three instances not two. But those were just a sampling of the ten or more times this question as been asked, and answered, in one form or another. – beyowulf Nov 27 '15 at 03:02
  • @beyowulf: I believe you have solved this question in the only elegant manner. My comment was directed at Daniele B, wondering if being told *you can't* was an acceptable response. – SwiftArchitect Nov 27 '15 at 07:00
  • @SwiftArchitect agreed. Sorry for the misunderstanding. – beyowulf Nov 27 '15 at 23:03
  • To expand on what @beyowulf said, you can also uncheck "Show navigation bar" in the attributes inspector for the navigation controller. – Shane Creighton-Young Mar 30 '17 at 20:18
4

--Swift 3.0--

Armin's solution adapted for swift 3. New -> File -> Cocoa Touch Class -> Class: ... (Subclass: UIStoryboardSegue).

import UIKit

class SlideHorSegue: UIStoryboardSegue {
    override func perform() {

        //credits to http://www.appcoda.com/custom-segue-animations/

        let firstClassView = self.source.view
        let secondClassView = self.destination.view

        let screenWidth = UIScreen.main.bounds.size.width
        let screenHeight = UIScreen.main.bounds.size.height

        secondClassView?.frame = CGRect(x: screenWidth, y: 0, width: screenWidth, height: screenHeight)

        if let window = UIApplication.shared.keyWindow {

            window.insertSubview(secondClassView!, aboveSubview: firstClassView!)

            UIView.animate(withDuration: 0.4, animations: { () -> Void in

                firstClassView?.frame = (firstClassView?.frame)!.offsetBy(dx: -screenWidth, dy: 0)
                secondClassView?.frame = (secondClassView?.frame)!.offsetBy(dx: -screenWidth, dy: 0)

            }) {(Finished) -> Void in

                self.source.navigationController?.pushViewController(self.destination, animated: false)

            }

        }

    }

}

In storyboard: mark your segue as "custom segue", and to assign the new class to it.

Note: If you have a UIScrollView in your detailesVC, this won't work.

Mr_Vlasov
  • 515
  • 2
  • 6
  • 25
3

It sounds as though the new view controller is presenting modally. If you embed the detailViewController into a UINavigationController and push the new controller it will animate from right to left and should show a back button too by default.

mylogon
  • 2,772
  • 2
  • 28
  • 42
  • 2
    I just inserted a UINavigationController between the MasterViewController and DetailViewController, but it still shows from the bottom – Daniele B Nov 20 '15 at 13:47
1

When you have a compact-width screen,"Show Detail" segue fall back to modal segue automatically.So your DetailViewController will appear vertically as the default modal segue.

You can use a UIViewControllerTransitioningDelegate to custom the animation of a modal segue.

Here is an example to achieve the horizontal animation:
1. set the transitioningDelegate and it's delegate method

class MasterViewController: UITableViewController,UIViewControllerTransitioningDelegate {

//prepare for segue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "showDetail" {
        let detailVC = segue.destinationViewController as! DetailViewController
        detailVC.transitioningDelegate = self
 //     detailVC.detailItem = object//Configure detailVC
    } 
}

//UIViewControllerTransitioningDelegate
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return LeftTransition()
}

func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    let leftTransiton = LeftTransition()
    leftTransiton.dismiss = true
    return leftTransiton
}

}    

2: a custom UIViewControllerAnimatedTransitioning : LeftTransition

import UIKit

class LeftTransition: NSObject ,UIViewControllerAnimatedTransitioning {
var dismiss = false

func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
    return 2.0
}

    func animateTransition(transitionContext: UIViewControllerContextTransitioning){
    // Get the two view controllers
    let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
    let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
    let containerView = transitionContext.containerView()!

    var originRect = containerView.bounds
    originRect.origin = CGPointMake(CGRectGetWidth(originRect), 0)

    containerView.addSubview(fromVC.view)
    containerView.addSubview(toVC.view)

    if dismiss{
        containerView.bringSubviewToFront(fromVC.view)
        UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void in
                fromVC.view.frame = originRect
            }, completion: { (_ ) -> Void in
                fromVC.view.removeFromSuperview()
                transitionContext.completeTransition(true )
        })
    }else{
        toVC.view.frame = originRect
        UIView.animateWithDuration(transitionDuration(transitionContext),
            animations: { () -> Void in
                toVC.view.center = containerView.center
            }) { (_) -> Void in
                fromVC.view.removeFromSuperview()
                transitionContext.completeTransition(true )
        }
    }
}
}
wj2061
  • 6,778
  • 3
  • 36
  • 62