简体   繁体   English

OS X storyboard 在 applicationDidFinishLaunching 之前调用 viewDidLoad

[英]OS X storyboard calls viewDidLoad before applicationDidFinishLaunching

I created a Mac OS X application in Xcode using storyboards.我使用情节提要在 Xcode 中创建了一个 Mac OS X 应用程序。 For some reason the applicationDidFinishLaunching method in the AppDelegate is being called after viewDidLoad in the NSViewControllers.由于某种原因,AppDelegate 中的applicationDidFinishLaunching方法在 NSViewControllers 中的viewDidLoad之后被调用。 As with iOS apps, I thought viewDidLoad is supposed to be called before applicationDidFinishLaunching ?与 iOS 应用程序一样,我认为viewDidLoad应该在applicationDidFinishLaunching之前调用? Do storyboards in OS X apps initialize the view controllers before the app has finished launching? OS X 应用程序中的故事板是否会在应用程序完成启动之前初始化视图控制器?

I am using the applicationDidFinishLaunching method to register default settings into NSUserDefaults.我正在使用applicationDidFinishLaunching方法将默认设置注册到 NSUserDefaults。 Unfortunately, registering the default values is happening after the views in the storyboard are loaded.不幸的是,注册默认值是在加载 storyboard 中的视图之后发生的。 Therefore, when I set up the view in each view controller using viewDidLoad , the defaults data in NSUserDefaults has not been set.因此,当我使用viewDidLoad在每个视图 controller 中设置视图时,尚未设置 NSUserDefaults 中的默认数据。 If I can't use applicationDidFinishLaunching to register NSUserDefaults in OS X storyboard apps, then how I set the defaults before viewDidLoad is called?如果我不能使用applicationDidFinishLaunching在 OS X storyboard 应用程序中注册 NSUserDefaults,那么在调用viewDidLoad之前如何设置默认值?

To fix this issue, in the Main.storyboard in Xcode, I turned off "Is Initial Controller" for the main window.为了解决这个问题,在Main.storyboard的 Main.storyboard 中,我关闭了主 window 的“是初始控制器”。 I assigned a storyboard ID to the main window as "MainWindow".我将 storyboard ID 分配给主 window 作为“MainWindow”。 Then in the AppDelegate I entered the following code:然后在 AppDelegate 中输入以下代码:

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    func applicationDidFinishLaunching(aNotification: NSNotification) {

        let storyboard = NSStoryboard(name: "Main", bundle: nil)
        let mainWindow = storyboard.instantiateControllerWithIdentifier("MainWindow") as! NSWindowController
        mainWindow.showWindow(nil)
        mainWindow.window?.makeKeyAndOrderFront(nil)

    }
}

The app does not crash but now the window never appears.该应用程序没有崩溃,但现在 window 永远不会出现。 The following image displays the storyboard I'm working with:下图显示了我正在使用的 storyboard:

在此处输入图像描述

Correct, the lifecycle is a wee bit different in OS X.正确,生命周期在 OS X 中有点不同。

Instead of letting the storyboard be your initial interface (this is defined in the General settings of your project), you can instead set up a MainMenu xib file and designate that as your main interface, then in your applicationDidFinishLaunching method in your AppDelegate you can programmatically instantiate your storyboard after you have completed your other initialization code.您可以设置一个 MainMenu xib 文件并将其指定为主界面,然后在 AppDelegate 的 applicationDidFinishLaunching 方法中以编程方式,而不是让故事板成为您的初始界面(这在项目的常规设置中定义)完成其他初始化代码后,实例化您的故事板。

I recommend checking out Cocoa Programming for OS X: The Big Nerd Ranch Guide if you haven't already;如果您还没有,我建议您查看OS X 的 Cocoa Programming: The Big Nerd Ranch Guide one nice thing they do in their book is actually have you get rid of some of the default Xcode template stuff and instead they have you set up your initial view controller the "right" way by doing it explicitly.他们在书中所做的一件好事实际上是让您摆脱了一些默认的 Xcode 模板内容,而是让您通过明确地设置“正确”的方式来设置初始视图控制器。

You might put something like this in your applicationDidFinishLaunching:您可以在 applicationDidFinishLaunching 中放置类似的内容:

NSStoryboard *mainStoryboard = [NSStoryboard storyboardWithName:@"Main" bundle:nil];
MyWindowController *initialController = (MyWindowController *)[mainStoryboard instantiateControllerWithIdentifier:@"myWindowController"];
[initialController showWindow:self];
[initialController.window makeKeyAndOrderFront:self];

This assumes that you've already changed "Main Interface" to something like MainMenu.xib.这假设您已经将“主界面”更改为类似 MainMenu.xib 的内容。

As the question's author mentioned:正如问题的作者所提到的:

The app does not crash but now the window never appears.该应用程序不会崩溃,但现在该窗口永远不会出现。

Despite Nicolas and Aaron both helpful answers (I already adquire the book you recommended, Aaron, and will start implementing the two-storyboard pattern your way, Nicolas), neither solve the specific problem of the window not showing.尽管NicolasAaron 的回答都很有帮助(我已经阅读了你推荐的书,Aaron,并将开始按照你的方式实现双故事板模式,Nicolas),但都没有解决窗口未显示的具体问题。

Solution解决方案

You need make your WindowController instance live outside applicationDidFinishLaunching(_:) 's scope.你需要让你的 WindowController 实例在applicationDidFinishLaunching(_:)的范围之外。 To accomplish it, you could declare a NSWindowController propriety for your AppDelegate class and persist your created WindowController instance there.要完成它,您可以为AppDelegate类声明一个NSWindowController属性,并将您创建的 WindowController 实例保留在那里。

Implementation实施

In Swift 4.0在 Swift 4.0 中

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    var mainWindowController: NSWindowController?

    func applicationDidFinishLaunching(_ aNotification: NSNotification) {

        let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil)
        let mainWindow = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "MainWindow")) as! NSWindowController
        mainWindow.showWindow(self)
        mainWindow.window?.makeKeyAndOrderFront(self)

        // After applicationDidFinishLaunching(_:) finished, the WindowController instance will persist.
        self.mainWindowController = mainWindow
    }
}

In addition to Aaron's answer , I found out that the separate Interface Builder file containing the menu alone (separate from the window and view controller) does not need to be a xib;除了Aaron 的回答之外,我发现单独包含菜单的单独 Interface Builder 文件(与窗口和视图控制器分开)不需要是 xib; it too can be a storyboard.它也可以是故事板。 This enables us to easily fix as follows:这使我们能够轻松修复如下:

  1. Duplicate your original storyboard (Main.storyboard) and rename it to " Menu .storyboard"复制您的原始故事板 (Main.storyboard)并将重命名为“ Menu .storyboard”
  2. Delete the window controller and view controller from Menu.storyboard从 Menu.storyboard 中删除窗口控制器和视图控制器
  3. Delete the app menu bar from Main.storyboard.从 Main.storyboard 中删除应用程序菜单栏。
  4. Change your app target's "Main Interface" from "Main.storyboard" to "Menu.storyboard".将您的应用程序目标的“主界面”从“Main.storyboard”更改为“Menu.storyboard”。

(I tried it in my app and it works) (我在我的应用程序中尝试过它并且有效)

This avoids having to re-create the app's main menu from scratch, or to figure out how to transplant your existing one from "Main.stroyboard" to "Menu.xib".这避免了必须从头开始重新创建应用程序的主菜单,或者弄清楚如何将现有菜单从“Main.stroyboard”移植到“Menu.xib”。

The other solution is registering NSApplicationDidFinishLaunchingNotification in your view controllers and move your code from viewDidLoad to applicationDidFinishLaunchingNotification:另一个解决方案是在您的视图控制器中注册NSApplicationDidFinishLaunchingNotification并将您的代码从viewDidLoad移动到applicationDidFinishLaunchingNotification:

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(applicationDidFinishLaunchingNotification:)
                                             name:NSApplicationDidFinishLaunchingNotification
                                           object:nil];

Based on my previous answer here根据我之前的回答here

If you want a piece of code to be run before everything, you can override the AppDelegate 's init() like this:如果你想在一切之前运行一段代码,你可以像这样覆盖AppDelegateinit()

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    override init() {
        DoSomethingBeforeEverthing()       // You code goes here
        super.init()
    }
...
}

Few points to remember:要记住的几点:

  1. You might want to check, what's allowed/can be done here, ie before app delegate is initialized您可能想检查一下,这里允许/可以做什么,即在初始化应用委托之前
  2. Also cleaner way would be, subclass the AppDelegate , and add new delegate method that you call from init, say applicationWillInitialize and if needed applicationDidInitialize同样更干净的方法是,将AppDelegate子类化,并添加您从 init 调用的新委托方法,例如applicationWillInitialize和如果需要applicationDidInitialize

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM