Раскадровка 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; это тоже может быть раскадровка. Это позволяет нам легко исправить следующее:
- Дублируйте свой оригинальный раскадровку (Main.storyboard) и переименуйте его в "Menu.storyboard"
- Удалить контроллер окна и просмотреть контроллер из Menu.storyboard
- Удалить строку меню приложения из Main.storyboard.
- Измените "Основной интерфейс" целевого приложения с "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()
}
...
}
Несколько моментов, которые нужно помнить:
- Возможно, вы захотите проверить, что разрешено/можно сделать здесь, т.е. до инициализации делегата приложения.
- Также более чистым способом было бы создание подкласса
AppDelegate
и добавьте новый метод делегата, который вы вызываете из инициализации, скажемapplicationWillInitialize
и если нужноapplicationDidInitialize