122

consider the following scenario: I have a storyboard-based app. I add a ViewController object to the storyboard, add the class files for this ViewController into the project and specify the name of the new class in the IB identity inspector. Now how am I going to refer to this ViewController programmatically from the AppDelegate? I've made a variable with the relevant class and turned it into an IBOutlet property, but I don't see any way of being able to refer to the new ViewController in code - any attempt to ctrl-drag a connection doesn't work.

i.e. within the AppDelegate I can get to the base ViewController like this

(MyViewController*) self.window.rootViewController

but how about any other ViewController contained within the storyboard?

Matthias D
  • 1,571
  • 2
  • 13
  • 17
  • Check [this answer](http://stackoverflow.com/a/36158926/1223728). It is swift, but the languages are similar. – Borzh Mar 22 '16 at 15:49

5 Answers5

165

Have a look at the documentation for -[UIStoryboard instantiateViewControllerWithIdentifier:]. This allows you to instantiate a view controller from your storyboard using the identifier that you set in the IB Attributes Inspector:

enter image description here

EDITED to add example code:

UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"MainStoryboard"
                                                         bundle: nil];

MyViewController *controller = (MyViewController*)[mainStoryboard 
                    instantiateViewControllerWithIdentifier: @"<Controller ID>"];
Robin Summerhill
  • 13,715
  • 3
  • 39
  • 37
  • Hi Robin, thanks for that! I looked at this doc but got the words instantiate and initialise mixed up... this gets us there (after following your instruction:) (damn the lack of code formatting in replies...) UIStoryboard* mainStoryboard = [UIStoryboard storyboardWithName: @"MainStoryboard" bundle: nil]; MyViewController* thisController = (MyViewController*) [mainStoryboard instantiateViewControllerWithIdentifier: @"myvc"]; – Matthias D Nov 18 '11 at 20:16
  • I've added your example code to the answer with formatting for anyone who is looking at this. – Robin Summerhill Nov 18 '11 at 21:39
  • 24
    if you're making a universal app be sure to use MainStoryboard_iPhone/MainStoryboard_iPad otherwise you'll get a crash. – roocell Dec 18 '11 at 20:42
  • can you please tell me, how to open this view controller also. – carbonr Apr 15 '12 at 13:19
  • This code works fine for me. But it seems like the address of the instance of the main view controller in the app delegate is different than the one created later. Any way of solving this? – bashan May 27 '12 at 17:48
  • This instantiate tells me that it makes a duplicate of ViewController inside storyboard, is it a reference to the one inside the storyboard or a duplicate, if duplicate how can we access the one inside the storyboard –  Jun 10 '12 at 16:44
  • @iPhoneDeveloper: After some testing, I discovered that it created a new instance of ViewController. It is not a reference to the one inside the storyboard. So the old delegate methods pattern is still needed to be used to pass back data to the parent controller. – user523234 Jul 06 '12 at 01:39
  • 16
    From within the delegate you can access the storyboard instance loaded by your info.plist like this: `[[[self window] rootViewController] storyboard]` According to the docs this will return the "storyboard from which the view controller originated." (or _nil_ if it didn't come from a storyboard). From that UIStoryboard* you can use the instantiate calls that @RobinSummerhill mentioned. Note that Storyboards instantiate new instances of your viewControllers (_scenes_) as they are needed and doesn't re-use those that were previously viewed. – Tad Bumcrot Jul 08 '12 at 12:16
  • @roocell : Why there would be crash? I have two storyboard as `MainStoryboard` and `MainStoryboard_iPad`. Nothing goes wrong. – Fahim Parkar Feb 05 '13 at 11:42
  • This does not give you a pointer to the VC within the storyboard (Which is what the OP asked for) this instantiates (as in the method name) a new instance. – Myron Slaw Jul 02 '13 at 04:57
  • 'the VC within the storyboard' makes no sense. There are no instances 'in' the storyboard. The storyboard is essentially the file saved from InterfaceBuilder and you need to instantiate objects specified within it. – Robin Summerhill Jul 03 '13 at 16:36
  • 6
    I believe the OP wants to know HOW TO GET AT the CURRENTLY RUNNING vc. Not how to load one. Exactly as he explains, you used to be able to say this **self.window.rootViewController** and now you can't any more. – Fattie Mar 31 '14 at 12:03
41

If you use XCode 5 you should do it in a different way.

  • Select your UIViewController in UIStoryboard
  • Go to the Identity Inspector on the right top pane
  • Check the Use Storyboard ID checkbox
  • Write a unique id to the Storyboard ID field

Then write your code.

// Override point for customization after application launch.

if (<your implementation>) {
    UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"Main" 
                                                             bundle: nil];
    YourViewController *yourController = (YourViewController *)[mainStoryboard 
      instantiateViewControllerWithIdentifier:@"YourViewControllerID"];
    self.window.rootViewController = yourController;
}

return YES;
mplewis
  • 440
  • 1
  • 10
  • 18
Faruk Toptas
  • 1,127
  • 12
  • 19
  • 4
    As far as I know and was able to try it out, __this__ is the updated answer. – gnclmorais Oct 21 '13 at 15:59
  • A storyboard's name is its filename without the extension so it could also be "Main_iPhone" or "Main_iPad" if you've set up your project that way. – Brian White Feb 07 '14 at 21:33
  • and then how do we transition back to the original rootViewController after login. At least to my knowledge of iOS 7 and Xcode 5.0.2 - changing the root view controller back will immediate and without animation switch the view hierarchy. The UX team have murdered coders for less. – lol Feb 20 '14 at 00:33
  • How would I do that using Swift? – Leo Dabus Nov 30 '14 at 08:17
8

Generally, the system should be handling view controller instantiation with a storyboard. What you want is to traverse the viewController hierarchy by grabbing a reference to the self.window.rootViewController as opposed to initializing view controllers, which should already be initialized correctly if you've setup your storyboard properly.

So, let's say your rootViewController is a UINavigationController and then you want to send something to its top view controller, you would do it like this in your AppDelegate's didFinishLaunchingWithOptions:

UINavigationController *nav = (UINavigationController *) self.window.rootViewController;
MyViewController *myVC = (MyViewController *)nav.topViewController;
myVC.data = self.data;

In Swift if would be very similar:

let nav = self.window.rootViewController as! UINavigationController;
let myVC = nav.topViewController as! MyViewController
myVc.data = self.data

You really shouldn't be initializing view controllers using storyboard id's from the app delegate unless you want to bypass the normal way storyboard is loaded and load the whole storyboard yourself. If you're having to initialize scenes from the AppDelegate you're most likely doing something wrong. I mean imagine you, for some reason, want to send data to a view controller way down the stack, the AppDelegate shouldn't be reaching way into the view controller stack to set data. That's not its business. It's business is the rootViewController. Let the rootViewController handle its own children! So, if I were bypassing the normal storyboard loading process by the system by removing references to it in the info.plist file, I would at most instantiate the rootViewController using instantiateViewControllerWithIdentifier:, and possibly its root if it is a container, like a UINavigationController. What you want to avoid is instantiating view controllers that have already been instantiated by the storyboard. This is a problem I see a lot. In short, I disagree with the accepted answer. It is incorrect unless the posters means to remove loading of the storyboard from the info.plist since you will have loaded 2 storyboards otherwise, which makes no sense. It's probably not a memory leak because the system initialized the root scene and assigned it to the window, but then you came along and instantiated it again and assigned it again. Your app is off to a pretty bad start!

smileBot
  • 18,797
  • 7
  • 60
  • 62
0
    UIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"Tutorial" bundle:nil];
    self.window.rootViewController = [storyboard instantiateInitialViewController];
Ofir Malachi
  • 842
  • 9
  • 16
0

For iOS 13+

in SceneDelegate:

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options 
connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }
    window = UIWindow(windowScene: windowScene)
    let storyboard = UIStoryboard(name: "Main", bundle: nil) // Where "Main" is the storyboard file name
    let vc = storyboard.instantiateViewController(withIdentifier: "ViewController") // Where "ViewController" is the ID of your viewController
    window?.rootViewController = vc
    window?.makeKeyAndVisible()
}
Vadim Zhuk
  • 101
  • 1
  • 4