207

I have a common UIViewController that all my UIViewsControllers extend to reuse some common operations.

I want to set up a segue on this "Common" UIViewController so that all the other UIViewControllers inherit.

I am trying to figure out how do I do that programmatically.

I guess that the question could also be how do I set a segue for all my UIViewControllers without going into the story board and do them by hand.

Alex Cio
  • 6,014
  • 5
  • 44
  • 74
Tiago Veloso
  • 8,513
  • 17
  • 64
  • 85

13 Answers13

347

I thought I would add another possibility. One of the things you can do is you can connect two scenes in a storyboard using a segue that is not attached to an action, and then programmatically trigger the segue inside your view controller. The way you do this, is that you have to drag from the file's owner icon at the bottom of the storyboard scene that is the segueing scene, and right drag to the destination scene. I'll throw in an image to help explain.

enter image description here

A popup will show for "Manual Segue". I picked Push as the type. Tap on the little square and make sure you're in the attributes inspector. Give it an identifier which you will use to refer to it in code.

enter image description here

Ok, next I'm going to segue using a programmatic bar button item. In viewDidLoad or somewhere else I'll create a button item on the navigation bar with this code:

UIBarButtonItem *buttonizeButton = [[UIBarButtonItem alloc] initWithTitle:@"Buttonize"
                                                                    style:UIBarButtonItemStyleDone
                                                                   target:self
                                                                   action:@selector(buttonizeButtonTap:)];
self.navigationItem.rightBarButtonItems = @[buttonizeButton];

Ok, notice that the selector is buttonizeButtonTap:. So write a void method for that button and within that method you will call the segue like this:

-(void)buttonizeButtonTap:(id)sender{
    [self performSegueWithIdentifier:@"Associate" sender:sender];
    }

The sender parameter is required to identify the button when prepareForSegue is called. prepareForSegue is the framework method where you will instantiate your scene and pass it whatever values it will need to do its work. Here's what my method looks like:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:@"Associate"])
    {
        TranslationQuizAssociateVC *translationQuizAssociateVC = [segue destinationViewController];
        translationQuizAssociateVC.nodeID = self.nodeID; //--pass nodeID from ViewNodeViewController
        translationQuizAssociateVC.contentID = self.contentID;
        translationQuizAssociateVC.index = self.index;
        translationQuizAssociateVC.content = self.content;
    }
}

I tested it and it works.

starball
  • 20,030
  • 7
  • 43
  • 238
SmileBot
  • 19,393
  • 7
  • 65
  • 62
  • @MichaelRowe how does this eliminate the need for segues? As i see it you still have to drag and drop on Storyboard to the destination controller.. – aherrick Mar 26 '14 at 23:08
  • @MichaelRowe this doesn't eliminate the need for segues. What this does is let you segue between view controllers that are built in code instead of in interface builder. – Matt Apr 29 '14 at 10:49
  • @Matt it actually just go me completely rethinking how I setup my app... After a complete rewrite of all the UI I no longer use any segues.. – Michael Rowe May 11 '14 at 20:47
  • @cocoanut i am getting the error as "Application tried to present modally an active controller" any help regarding this.. – Bala Jul 05 '14 at 10:39
  • 1
    Manual Segue "Push" is deprecated, use "Show". [This answer](http://stackoverflow.com/questions/24184003/adaptive-segue-in-storyboard-xcode-6-is-push-deprecated) has more details. @smileBot please update the answer. – NAbbas Mar 15 '16 at 19:51
  • This worked for me, but i had to use "Show" instead of "Push" and i didn't use the last method in this answer and it still worked. Thank you a lot +1!!!! – Lazar Kukolj Jul 29 '16 at 22:41
  • Maybe I don't understand the question, which would mean I do not understand the answer … my goal is to change the source of the UIStoryboardSegue, so I can push it from a presented view controller. If I want to do that, the NIB seems to be the right solution – below Feb 18 '17 at 10:05
170

By definition a segue can't really exist independently of a storyboard. It's even there in the name of the class: UIStoryboardSegue. You don't create segues programmatically - it is the storyboard runtime that creates them for you. You can normally call performSegueWithIdentifier: in your view controller's code, but this relies on having a segue already set up in the storyboard to reference.

What I think you are asking though is how you can create a method in your common view controller (base class) that will transition to a new view controller, and will be inherited by all derived classes. You could do this by creating a method like this one to your base class view controller:

- (IBAction)pushMyNewViewController
{
    MyNewViewController *myNewVC = [[MyNewViewController alloc] init];

    // do any setup you need for myNewVC

    [self presentModalViewController:myNewVC animated:YES];
}

and then in your derived class, call that method when the appropriate button is clicked or table row is selected or whatever.

jonkroll
  • 15,682
  • 4
  • 50
  • 43
  • 4
    Thanks. It's a shame we can't do it programmatically. It would really boost up the quality of source code (less duplication is always good). I'll have a go with your suggestion. – Tiago Veloso Mar 13 '12 at 08:37
  • 1
    there are situations in which is not possible to create segues from IB, in example: if i want to bind the disclosure button of a mapview annotation i have not any item to drag in IB at design time. Anyway the fact is "we can't" so let's do it manually. – kappa Mar 16 '12 at 11:19
  • 2
    @jonkroll is it possible to call / perform segue from switch statement i.e. based on what index i have? – codejunkie Apr 14 '12 at 10:57
  • 5
    @codejunkie: Yes, you can do that. You would use the `UIViewController` method named `performSegueWithIdentifier:sender:` for this. – jonkroll Apr 15 '12 at 16:28
  • 2
    I’ve been creating and performing segue programmatically (see my answer). Anything wrong with my code, then, if your answer is correct? – Jean-Philippe Pellet May 23 '13 at 09:52
  • 15
    Update for iOS 6+ : `UIView`'s `presentModalViewController:animated:` is deprecated. From the [docs](http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/DeprecationAppendix/AppendixADeprecatedAPI.html#presentModalViewController:animated:)- *(Deprecated in iOS 6.0. Use presentViewController:animated:completion: instead.)* – user Jul 23 '13 at 04:17
  • The swift equivalent to this selected answer is here: http://stackoverflow.com/questions/24035984/instantiate-and-present-a-viewcontroller-in-swift – Ed Manners Jun 17 '15 at 11:04
  • #jonkroll : is it possible to push a UIViewController 'A' (that is not inside framework) from a UIViewController 'B' (B is view controller inside that framework). – Abhishek Thapliyal May 11 '16 at 10:20
  • 1
    The definition of a segue never excluded an entirely programmatic implementation; that defies logic. Interface Builder is just a quick way to write code; there's nothing at all that it does differently than that. See my answer below... – James Bush Dec 18 '16 at 09:49
82

I've been using this code to instantiate my custom segue subclass and run it programmatically. It seems to work. Anything wrong with this? I'm puzzled, reading all the other answers saying it cannot be done.

UIViewController *toViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"OtherViewControllerId"];
MyCustomSegue *segue = [[MyCustomSegue alloc] initWithIdentifier:@"" source:self destination:toViewController];
[self prepareForSegue:segue sender:sender];
[segue perform];
Jean-Philippe Pellet
  • 59,296
  • 21
  • 173
  • 234
  • 3
    It is a custom subclass of `UIStoryboardSegue`. – Jean-Philippe Pellet Jun 16 '13 at 08:52
  • Whatever you want. Most likely, the button that was tapped and triggered the segue. The OS doesn't care. – Jean-Philippe Pellet Jul 06 '13 at 09:07
  • 7
    @MarkAmery A lot of people (including me) avoid using storyboards. They're hard to merge, and there's no compile-time check that the ID I’m passing to `performSegueWithIdentifier:` is really defined in the storyboard. I avoid all problems if I create the segue myself. – Jean-Philippe Pellet Jul 18 '13 at 15:36
  • 2
    Merci beaucoup Jean-Philippe! I have MANY pages that all require an exit to a Main Menu, using a custom segue animation. Creating all the links on the storyboard would have been ridiculous. Very useful code, merci. – Custom Bonbons Oct 02 '13 at 10:49
  • 3
    I agree with Jean-Philippe. Managing storyboard is a pain in the butt. Of course it's easy to click your way through crating few views and adding a segue here and a segue there, but managing 6 views with 16 segues defined in XML, when you have three developers all fiddling with it is terrible. Anyway, the point is: code gives you control, xml generated by xcode does not. – Krystian Oct 03 '13 at 16:46
  • 3
    I see a crash in [segue perform] in iOS7, not sure if anyone else is experiencing this. – Eric Chen Jan 12 '14 at 13:01
  • @fjlksahfob Why would it have to exist? It's been 3 years now and I don't work on that project, but IIRC we didn't use storyboards at all. – Jean-Philippe Pellet May 18 '16 at 14:07
  • 1
    ```UIViewController *toViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"OtherViewControllerId"];``` this line in your answer means the controller has to be instantiated from a storyboard. If you don't have a controller with that ID your app will crash... – fjlksahfob May 19 '16 at 19:17
  • 1
    @fjlksahfob Ah, sure, in this case, but nothing prevents me from actually instantiating it programmatically as well. – Jean-Philippe Pellet May 20 '16 at 08:28
  • Using this, the app crashes and I get the error "A segue must either have a performHandler or it must override -perform". – Minimi Nov 07 '16 at 18:52
  • @Minimi This means your UIStoryboardSegue subclass must override the perform method or you must use UIStoryboardSegue's alternate constructor that implements the perform block. – Charlton Provatas Feb 15 '17 at 20:09
44

Guess this is answered and accepted, but I just would like to add a few more details to it.

What I did to solve a problem where I would present a login-view as first screen and then wanted to segue to the application if login were correct. I created the segue from the login-view controller to the root view controller and gave it an identifier like "myidentifier".

Then after checking all login code if the login were correct I'd call

[self performSegueWithIdentifier: @"myidentifier" sender: self];

My biggest misunderstanding were that I tried to put the segue on a button and kind of interrupt the segue once it were found.

qrikko
  • 2,483
  • 2
  • 22
  • 35
32

You have to link your code to the UIStoryboard that you're using. Make sure you go into YourViewController in your UIStoryboard, click on the border around it, and then set its identifier field to a NSString that you call in your code.

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" 
                                                     bundle:nil];
YourViewController *yourViewController = 
 (YourViewController *)
  [storyboard instantiateViewControllerWithIdentifier:@"yourViewControllerID"];
[self.navigationController pushViewController:yourViewController animated:YES];
Alex Cio
  • 6,014
  • 5
  • 44
  • 74
Jeff Grimes
  • 2,300
  • 1
  • 23
  • 23
  • 1
    I get this, but what if the viewController I want to present is embedded in a NavigationController in the storyboard? From what I can find, I can initialize a NavigationController to embed it in but in the storyboard, I already have push segues setup for the view that needs to be presented. – jhilgert00 Dec 14 '12 at 02:11
  • 1
    can you elaborate on this? I think this is the problem I am having, but can't seem t find how/where to do this... – jesses.co.tt Jul 14 '13 at 00:00
  • 1
    Even this solution is a correct one, It's about avoiding any segue, but the question is about segue. In this way you can connect or make a transition between two scene WITHOUT segue in the storyboards. – BootMaker Oct 17 '16 at 13:06
16

For controllers that are in the storyboard.

jhilgert00 is this what you were looking for?

-(IBAction)nav_goHome:(id)sender {
UIViewController *myController = [self.storyboard instantiateViewControllerWithIdentifier:@"HomeController"];
[self.navigationController pushViewController: myController animated:YES];

}

OR...

[self performSegueWithIdentifier:@"loginMainSegue" sender:self];
user1723341
  • 1,107
  • 8
  • 7
3

well , you can create and also can subclass the UIStoryBoardSegue . subclassing is mostly used for giving custom transition animation.

you can see video of wwdc 2011 introducing StoryBoard. its available in youtube also.

http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIStoryboardSegue_Class/Reference/Reference.html#//apple_ref/occ/cl/UIStoryboardSegue

jogi47
  • 105
  • 2
  • 9
3

Storyboard Segues are not to be created outside of the storyboard. You will need to wire it up, despite the drawbacks.

UIStoryboardSegue Reference clearly states:

You do not create segue objects directly. Instead, the storyboard runtime creates them when it must perform a segue between two view controllers. You can still initiate a segue programmatically using the performSegueWithIdentifier:sender: method of UIViewController if you want. You might do so to initiate a segue from a source that was added programmatically and therefore not available in Interface Builder.

You can still programmatically tell the storyboard to present a view controller using a segue using presentModalViewController: or pushViewController:animated: calls, but you'll need a storyboard instance.

You can call UIStoryboards class method to get a named storyboard with bundle nil for the main bundle.

storyboardWithName:bundle:

Cameron Lowell Palmer
  • 21,528
  • 7
  • 125
  • 126
3

I'd like to add a clarification...

A common misunderstanding, in fact one that I had for some time, is that a storyboard segue is triggered by the prepareForSegue:sender: method. It is not. A storyboard segue will perform, regardless of whether you have implemented a prepareForSegue:sender: method for that (departing from) view controller.

I learnt this from Paul Hegarty's excellent iTunesU lectures. My apologies but unfortunately cannot remember which lecture.

If you connect a segue between two view controllers in a storyboard, but do not implement a prepareForSegue:sender: method, the segue will still segue to the target view controller. It will however segue to that view controller unprepared.

Hope this helps.

andrewbuilder
  • 3,629
  • 2
  • 24
  • 46
2

First of, suppose you have two different views in storyboard, and you want to navigate from one screen to another, so follow this steps:

1). Define all your views with class file and also storyboard id in identity inspector.

2). Make sure you add a navigation controller to the first view. Select it in the Storyboard and then Editor >Embed In > Navigation Controller

3). In your first class, import the "secondClass.h"

#import "ViewController.h
#import "secondController.h"

4). Add this command in the IBAction that has to perform the segue

secondController *next=[self.storyboard instantiateViewControllerWithIdentifier:@"second"];
[self.navigationController pushViewController:next animated:YES];

5). @"second" is secondview controller class, storyboard id.

Victor Rius
  • 4,566
  • 1
  • 20
  • 28
  • `self.storyboard` should be: `UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];` – masipcat Mar 15 '17 at 16:00
  • @masipcat and the Story board name might depend on how you set up your Xcode project, in mine it was "Main.storyboard" so I used `storyboardWithName:@"Main"` – ammianus Sep 10 '17 at 19:50
  • @sanket-chauhan if your first controller is not embedded in a Navigation Controller, you can also show the next view using `[self showDetailViewController:next sender:self];` or `[self showViewController:next sender:self];` – ammianus Sep 10 '17 at 19:52
1

I reverse-engineered and made an open source (re)implementation of UIStoryboard's segues: https://github.com/acoomans/Segway

With that library, you can define segues programmatically (without any storyboard).

Hope it may help.

acoomans
  • 360
  • 1
  • 4
0

A couple of problems, actually:

First, in that project you uploaded for us, the segue does not bear the "segue1" identifier:

no identifier

You should fill in that identifier if you haven't already.

Second, as you're pushing from table view to table view, you're calling initWithNibName to create a view controller. You really want to use instantiateViewControllerWithIdentifier.

jaydip
  • 1
0

Here is the code sample for Creating a segue programmatically:

class ViewController: UIViewController {
    ...
    // 1. Define the Segue
    private var commonSegue: UIStoryboardSegue!
    ...
    override func viewDidLoad() {
        ...
        // 2. Initialize the Segue
        self.commonSegue = UIStoryboardSegue(identifier: "CommonSegue", source: ..., destination: ...) {
            self.commonSegue.source.showDetailViewController(self.commonSegue.destination, sender: self)
        }
        ...
    }
    ...
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // 4. Prepare to perform the Segue
        if self.commonSegue == segue {
            ...
        }
        ...
    }
    ...
    func actionFunction() {
        // 3. Perform the Segue
        self.prepare(for: self.commonSegue, sender: self)
        self.commonSegue.perform()
    }
    ...
}
jqgsninimo
  • 6,562
  • 1
  • 36
  • 30
  • You are calling `self.prepare(for: self.commonSegue, sender: self)` from your action method. Then what's the point of comparing `if self.commonSegue == segue {...}` in `prepare(for:sender)` method? – nayem Jul 03 '17 at 03:33
  • @nayem: In `prepare(for:sender:)`, you can configure the destination view controller prior to it being displayed. Of course you can also do it in `actionFunction`. – jqgsninimo Jul 03 '17 at 05:58
  • @nayem: The reason I do this is to try to be consistent with the handling of other segues. – jqgsninimo Jul 03 '17 at 06:10