故事板 - 在AppDelegate中引用ViewController

122
考虑以下情况:我有一个基于Storyboard的应用程序。 我向Storyboard添加了一个ViewController对象,将该ViewController的类文件添加到项目中,并在IB identity inspector中指定新类的名称。 现在我该如何从AppDelegate中以编程方式引用此ViewController? 我已经创建了一个与相关类对应的变量,并将其转换为IBOutlet属性,但我看不到任何一种能够在代码中引用新的ViewController的方式-任何尝试进行ctrl-drag连接都不起作用。
例如,在AppDelegate中,我可以按照以下方式访问基础ViewController:
(MyViewController*) self.window.rootViewController

那么在故事版中包含的其他任何ViewController怎么办?


请查看这个答案。它是用Swift编写的,但两种语言很相似。 - Borzh
5个回答

165
请参阅文档,了解-[UIStoryboard instantiateViewControllerWithIdentifier:]的用法。这允许您通过IB Attributes Inspector中设置的标识符从故事板中实例化视图控制器:

enter image description here

编辑添加示例代码:

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

MyViewController *controller = (MyViewController*)[mainStoryboard 
                    instantiateViewControllerWithIdentifier: @"<Controller ID>"];

24
如果您正在制作一款通用应用程序,请确保使用MainStoryboard_iPhone/MainStoryboard_iPad,否则您的应用程序将崩溃。 - roocell
16
您可以在委托类中通过以下方式访问由info.plist加载的storyboard实例:[[[self window] rootViewController] storyboard] 根据文档,这将返回“视图控制器起源的故事板”(如果它不是来自故事板,则返回_nil_)。 从UIStoryboard*中,您可以使用@RobinSummerhill提到的实例化调用。 请注意,Storyboards会根据需要实例化您的视图控制器(_场景_)的新实例,并不会重用之前查看过的实例。 - Tad Bumcrot
这并不会给你一个指向故事板中VC的指针(这是OP所要求的),它实例化(就像方法名一样)一个新实例。 - Myron Slaw
“the VC within the storyboard” 没有意义。在故事板中没有实例。故事板本质上是从 InterfaceBuilder 保存的文件,您需要实例化其中指定的对象。 - Robin Summerhill
6
我相信OP想知道如何获取当前正在运行的vc,而不是如何加载一个vc。正如他所解释的那样,你以前可以使用self.window.rootViewController,但现在不能再这样做了。 - Fattie
显示剩余7条评论

42

如果您使用的是 XCode 5,您应该以不同的方式进行操作。

  • UIStoryboard 中选择您的 UIViewController
  • 转到右上角的 Identity Inspector
  • 勾选 Use Storyboard ID 复选框
  • Storyboard ID 字段中填写一个唯一的ID

然后编写您的代码。

// 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;

4
据我所知并尝试过,这个 是更新后的答案。 - gnclmorais
一个故事板的名称是它的文件名(不带扩展名),因此如果您已经按照这种方式设置了项目,它也可以是“Main_iPhone”或“Main_iPad”。 - Brian White
然后我们如何在登录后切换回原始的rootViewController呢?根据我对iOS 7和Xcode 5.0.2的了解,更改root视图控制器将立即且不带动画地切换视图层次结构。UX团队因为小问题已经杀了程序员。 - lol
我该如何使用Swift实现这个? - Leo Dabus

8
通常情况下,系统应该使用故事板来处理视图控制器的实例化。您需要做的是通过获取对self.window.rootViewController的引用来遍历viewController层次结构,而不是初始化视图控制器,如果您正确设置了故事板,则应该已正确初始化它们。
因此,假设您的rootViewController是一个UINavigationController,然后您想将某些内容发送到其顶部视图控制器,则可以在AppDelegate的didFinishLaunchingWithOptions中这样做:
UINavigationController *nav = (UINavigationController *) self.window.rootViewController;
MyViewController *myVC = (MyViewController *)nav.topViewController;
myVC.data = self.data;

在 Swift 中,这将非常相似:
let nav = self.window.rootViewController as! UINavigationController;
let myVC = nav.topViewController as! MyViewController
myVc.data = self.data

除非你想绕过Storyboard的正常加载方式并自己加载整个Storyboard,否则不应该在AppDelegate中使用Storyboard ID来初始化视图控制器。如果你必须从AppDelegate初始化场景,那么你很可能正在做一些错误的事情。比如,假设你想向一个深层的视图控制器发送数据,那么AppDelegate不应该到达视图控制器栈的深处设置数据。这不是它的职责。它的职责是rootViewController。让rootViewController处理它自己的子视图控制器!因此,如果我要通过从info.plist文件中删除对Storyboard的引用绕过系统的正常Storyboard加载过程,我最多只会使用instantiateViewControllerWithIdentifier:实例化rootViewController,如果它是一个包含容器,比如UINavigationController,可能也会实例化它的root。你要避免的是实例化已经被Storyboard实例化过的视图控制器,这是我经常看到的问题。简而言之,我不同意已接受的答案。除非发帖人的意思是要从info.plist中删除Storyboard的加载,否则它是不正确的,因为否则你将加载了两个Storyboard,这是没有意义的。这可能不是一个内存泄漏,因为系统已经初始化了root scene并将其分配给窗口,但是然后你又来实例化它并再次分配它。你的应用程序起步很糟糕!

0

iOS 13及以上版本:

在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()
}

0
    UIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"Tutorial" bundle:nil];
    self.window.rootViewController = [storyboard instantiateInitialViewController];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接