Search code examples
iosswiftstoryboardsegue

Is it better practice to performSegueWithIdentifier or presentViewController


If I am using storyboards, is it better practice to create a segue and use performSegueWithIdentifier or just use presentViewController to move the user from one View Controller to the next.

For this example, let's assume we are using Swift 5 and I have 2 view controllers: a TableView of Contacts on MainViewController and the details of that Contact on DetailViewController.

In my code, I am using presentViewController, but as I am new to iOS development, I'm not sure if this is the best approach.


Solution

  • Let's assume for purposes of illustration that the situation in the storyboard is as follows:

    ViewController1
        |----> modal segue "toVC2" emanating from ViewController1
            |----> ViewController2, identifier "VC2"
    

    Then in ViewController1 code there is absolutely no functional or effective difference between these two things:

    • Call performSegue with identifier "toVC2"
    • Call instantiateViewController on the storyboard with identifier "VC2" and call present

    Having said that, I will make a case for each way being "better" than the other.

    Why the segue might be better

    The segue in my example doesn't look much better because we still have to use code to call performSegue. However, suppose that the segue emanates not from the view controller but from a button in the view controller's view. That is an action segue and now the segue will be performed automatically when the user taps our button, with no code needed to perform it. You don't even need to know the identifier. (String identifiers are a great way to make a mistake.)

    Why instantiate and present might be better

    There is something very horrible about segues, whether it's an action segue or a manually triggered segue with performSegue — namely, when (as so often happens) you want to pass data from the first view controller to the second. If you use a segue, you must do the data-passing in an implementation of prepare(for:sender:). This is very messy because you have to check the segue identifier and cast the destination to the correct class. Moreover, if there is more than one segue, prepare becomes a messy bottleneck. And if we also called performSegue, we are operating in two different places — the method where we called performSegue, and the separate implementation of prepare.

    By contrast, if we call instantiateViewController, what comes back to us is the view controller instance itself. We still have to cast, but now we can pass the data right then and there, without "waiting" for prepare and without passing through a bottleneck. Thus this is a far more legible and encapsulated way of proceeding.

    Also, this may seem obvious, but a nice thing about saying present is that this tells us right there in the code what the transition is. If you just use a segue identifier, the fact that this is a modal segue is hidden off in the storyboard.

    Which do I prefer?

    Neither. I prefer not to use storyboards at all for presented view controllers if I can help it. I use a view controller and eponymous nib. That way, I simply instantiate the view controller itself and pass it the data and present it, with no need to cast and no need for any identifiers.

    So am I saying that storyboards are no good at all?

    No. Storyboards have the virtue of mapping out the scene structure of your app, especially if you do use segues. If you are not going to use a segue to a view controller, however, then I don't really see the point of keeping that view controller in a storyboard in the first place. The eponymous nib mechanism is clearer and cleaner. Moreover, I regard the prepare(for:sender:) mechanism as a near-fatal flaw in the entire storyboard architecture; a huge majority of mistakes shown on Stack Overflow are a consequence of this flaw.

    [EDIT Note that a lot of my objections to segues will fall away in iOS 13, where the source view controller will be able to use code to call an initializer of the destination view controller.]