[英]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.尽管Nicolas和Aaron 的回答都很有帮助(我已经阅读了你推荐的书,Aaron,并将开始按照你的方式实现双故事板模式,Nicolas),但都没有解决窗口未显示的具体问题。
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 实例保留在那里。
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:
这使我们能够轻松修复如下:
(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:如果你想在一切之前运行一段代码,你可以像这样覆盖
AppDelegate
的init()
:
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
override init() {
DoSomethingBeforeEverthing() // You code goes here
super.init()
}
...
}
Few points to remember:要记住的几点:
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.