I'm attempting to create a macOS application without a storyboard in Xcode 8 (stable) on macOS Sierra. However, my AppDelegate
is not even being initiated. Here's the code I have:
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
override init() {
super.init()
print("Init")
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
print("Finished launching")
// Create a window
window = NSWindow()
// Add the view controller
let viewController = ViewController()
window.contentView?.addSubview(viewController.view)
// Show the window
window.makeKeyAndOrderFront(nil)
}
}
Neither init
or applicationDidFinishLaunching(_ aNotification: Notification)
is being called. Any help would be much appreciated.
You need to do a few things here
NSMainStoryboardFile
key/value from the plist NSApplication
subclass and assign it to the Principal Class (NSPrincipalClass)
key. The name must be fully qualified with your module name.
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.
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.
@NSApplicationMain
via a workaroundI 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
.
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 for NSObject
, at least in macOS. You lose the ability to reference self
in the override init()
block, however.
Don't override init()
at all: Consider doing your init process during a lifecycle method like applicationWillFinishLaunching(:)
.
I don't recommend either of these, of course.
@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.
Info.plist
fileThe key:value pair for NSPrincipalClass
should keep its default value of:
<key>NSPrincipalClass</key>
<string>NSApplication</string>
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)
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With