Раскадровка OS X вызывает viewDidLoad перед применением ApplicationDidFinishLaunching

Я создал приложение Mac OS X в XCode, используя раскадровки. По какой-то причине applicationDidFinishLaunching метод в AppDelegate вызывается после viewDidLoad в NSViewControllers. Как и в приложениях для iOS, я подумал viewDidLoad должен быть вызван раньше applicationDidFinishLaunching? Раскадровки в приложениях OS X инициализируют контроллеры представления прежде, чем приложение закончило запуск?

Я использую applicationDidFinishLaunching метод регистрации настроек по умолчанию в NSUserDefaults. К сожалению, регистрация значений по умолчанию происходит после загрузки представлений в раскадровке. Поэтому, когда я настраиваю представление в каждом контроллере представления, используя viewDidLoadданные по умолчанию в NSUserDefaults не были установлены. Если я не могу использовать applicationDidFinishLaunching зарегистрировать NSUserDefaults в приложениях раскадровки OS X, то, как я установил значения по умолчанию перед viewDidLoad называется?

Чтобы исправить эту проблему, в Main.storyboard в XCode я отключил "Is Initial Controller" для главного окна. Я назначил ID раскадровки главному окну как "MainWindow". Затем в 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)

    }
}

Приложение не падает, но теперь окно никогда не появляется. На следующем рисунке показана раскадровка, с которой я работаю:

введите описание изображения здесь

5 ответов

Правильно, жизненный цикл немного отличается в OS X.

Вместо того, чтобы раскадровка была вашим начальным интерфейсом (это определено в общих настройках вашего проекта), вы можете вместо этого настроить xib-файл MainMenu и указать его в качестве основного интерфейса, а затем в методе applicationDidFinishLaunching в AppDelegate вы можете программно создать свою раскадровку после того, как вы выполнили другой код инициализации.

Я рекомендую проверить Программирование Какао для OS X: Руководство Ранчо Большого Nerd, если Вы еще не сделали; одна хорошая вещь, которую они делают в своей книге, на самом деле заключается в том, что вы избавляетесь от некоторых стандартных шаблонных шаблонов Xcode, и вместо этого они заставляют вас настроить свой первоначальный контроллер представления "правильным" способом, делая это явно.

Вы можете поместить что-то вроде этого в ваше приложение DidFinishLaunching:

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

Предполагается, что вы уже изменили "Основной интерфейс" на что-то вроде MainMenu.xib.

Как отметил автор вопроса:

Приложение не падает, но теперь окно никогда не появляется.

Несмотря на полезные ответы Николаса и Аарона (я уже получаю книгу, которую вы порекомендовали, Аарон, и начну реализовывать шаблон с двумя раскадровками, Николас), ни одна из них не решит конкретную проблему, когда окно не отображается.

Решение

Вы должны сделать ваш экземпляр WindowController живым снаружи applicationDidFinishLaunching(_:)Сфера. Для этого вы можете объявить NSWindowController уместность для вашего AppDelegate Класс и сохранить ваш созданный экземпляр WindowController там.

Реализация

В 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)

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

}

В дополнение к ответу Аарона я обнаружил, что отдельный файл Interface Builder, содержащий только меню (отдельно от контроллера окна и представления) , не обязательно должен быть xib; это тоже может быть раскадровка. Это позволяет нам легко исправить следующее:

  1. Дублируйте свой оригинальный раскадровку (Main.storyboard) и переименуйте его в "Menu.storyboard"
  2. Удалить контроллер окна и просмотреть контроллер из Menu.storyboard
  3. Удалить строку меню приложения из Main.storyboard.
  4. Измените "Основной интерфейс" целевого приложения с "Main.storyboard" на "Menu.storyboard".

(Я попробовал это в моем приложении, и это работает)

Это позволяет избежать необходимости заново создавать главное меню приложения или выяснить, как перенести существующее меню из "Main.stroyboard" в "Menu.xib".

Другое решение - регистрация NSApplicationDidFinishLaunchingNotification по вашему мнению контроллеров и переместить ваш код из viewDidLoad в applicationDidFinishLaunchingNotification:

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

Основываясь на моем предыдущем ответе здесь

Если вы хотите, чтобы часть кода запускалась перед всем, вы можете переопределить 'init()так:

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

Несколько моментов, которые нужно помнить:

  1. Возможно, вы захотите проверить, что разрешено/можно сделать здесь, т.е. до инициализации делегата приложения.
  2. Также более чистым способом было бы создание подклассаAppDelegateи добавьте новый метод делегата, который вы вызываете из инициализации, скажемapplicationWillInitializeи если нужноapplicationDidInitialize
Другие вопросы по тегам