NSApplicationDelegate not working without Storyboard
You need to do a few things here
- Delete
NSMainStoryboardFile
key/value from the plist - Create a
NSApplication
subclass and assign it to thePrincipal Class (NSPrincipalClass)
key.
The name must be fully qualified with your module name.
- Manually instantiate your delegate in your NSApplication subclass and assign it to the
delegate
property.
Make sure you keep a strong reference to your delegate object. Ive just used a let
here.
class GrookApplication: NSApplication {
let strongDelegate = AppDelegate()
override init() {
super.init()
self.delegate = strongDelegate
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
e.g a simple delegate.
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
override init() {
super.init()
print("wassup")
//conceptual proof of life init override
//wait until applicationDidFinishLaunching , specially for UI
}
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
print("yo! I'm alive")
window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 200, height: 200), styleMask: .titled, backing: .buffered, defer: false)
window.makeKeyAndOrderFront(nil)
}
}
EDIT 2018 Verified High Sierra
Do NOT try and do window or view controller initialisation inside init
this leads to double app initialisation issues and crashing. The app has not finished launching at this stage. Wait until applicationDidFinishLaunching
to fire any significant operations.
In case someone is looking for a Swift version (based on @WarrenBurtons answer).
AppDelegate
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow?
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
window = NSWindow(contentViewController: RootViewController())
window?.makeKeyAndOrderFront(self)
}
}
class RootViewController: NSViewController {
override func loadView() {
self.view = NSView()
self.view.frame = NSRect(x: 0, y: 0, width: 600, height: 400)
}
}
NSApplication subclass
import Cocoa
class Application: NSApplication {
let strongDelegate = AppDelegate()
override init() {
super.init()
self.delegate = strongDelegate
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Info.plist entry
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
...
<key>NSPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).Application</string>
...
</dict>
</plist>
I've also created a gist for this, that I will keep up to date for new Xcode / Swift versions. https://gist.github.com/florieger/7ac5e7155f6faf18666f92f7d82f6cbc
Edit: Make sure to delete the Main.storyboard / MainMenu.xib, otherwise you might end up with two Windows in the UI Debugger.
Warren Burton's accepted answer, utilising a strong reference to a @NSApplicationMain
-annotated AppDelegate instance no longer works. I've confirmed it myself on OS X High Sierra, and Alex Sieroshtan commented that it didn't work back in OS X Yosemite, either. The failure point, as Tyler Durden noted, was this message:
Assertion failure in -[X.XApplication init], /Library/Caches/com.apple.xbs/Sources/AppKit/AppKit-1504.82.104/AppKit.subproj/NSApplication.m:1778
2017-04-08 13:25:35.761585+0100 X
[9073:1059806][General] An uncaught exception was raised 2017-04-08 13:25:35.761601+0100 X
[9073:1059806][General] Creating more than one Application
I struggled with this myself for a good while, but came up with two up-to-date solutions by no small amount of experimentation.
Option 1: Continue to use @NSApplicationMain
via a workaround
I found that you can alter the code of the accepted answer to work around the bug. The way to do this is by not calling the super.init()
method in your class named AppDelegate
.
What?
Really. I think there is a too-eager assertion counting number of inits done by AppDelegate
(or some logic along these lines), and thus the call to super.init()
gets counted as well as the completion of the override init()
block. You have two options for workarounds here:
Don't call
super.init()
: This is actually possible and completely healthy forNSObject
, at least in macOS. You lose the ability to referenceself
in theoverride init()
block, however.Don't override
init()
at all: Consider doing your init process during a lifecycle method likeapplicationWillFinishLaunching(:)
.
I don't recommend either of these, of course.
Option 2: Give up on the @NSApplicationMain
method altogether
@NSApplicationMain
is just a macro which we can approximate ourselves. By some luck, I came across James H Fisher's blog post explaining how. I'll quote what matters in a moment.
If you have written @NSApplicationMain
anywhere, please delete it before proceeding with these instructions.
No need to alter your Info.plist
file
The key:value pair for NSPrincipalClass
should keep its default value of:
<key>NSPrincipalClass</key>
<string>NSApplication</string>
Use main.swift
instead of subclassing NSApplication
The file MUST be called main.swift
; it's a special exception to Swift's "Expressions are not allowed at the top level" rule.
import AppKit
let app = NSApplication.shared
let delegate = AppDelegate()
app.delegate = delegate
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
The logic
James H Fisher explains, referencing the NSApplication
documentation:
[Docs]
Every app must have exactly one instance of
NSApplication
(or a subclass ofNSApplication
). Your program’smain()
function should create this instance by invoking theshared()
class method.[James]
First,
main.swift
runsNSApplication.shared
, and assigns thisNSApplication
object tomyApp
. Notice the documentation refers to amain()
function, even though in Swift there is none! The equivalent is themain.swift
file.Next,
main.swift
instantiates yourAppDelegate
class, and assigns it as the.delegate
ofmyApp
. You can now see why the default project chooses to call the classAppDelegate
: it is set as the.delegate
on anNSApplication
.Finally,
main.swift
calls the functionNSApplicationMain(...)
... The functionNSApplicationMain(...)
is the entry point for Cocoa applications.NSApplicationMain(...)
never returns; instead, it sets up the UI event loop, and eventually exits using the Cexit(...)
function.
Additionally this StackOverflow post goes into some detail about why using sharedApplication
remedies the "Creating more than one Application" bug.
... That's all you need! Hope this serves to help somebody else.