How to open window in MacOS programmatically using Swift
April 27, 2019
Once I face a problem - “How can I create a normal window on Mac OS?”. Definitely it is possible to use XCode, generate new project using very modern tools and start writing events handling functions/callbacks/delegates/etc. But what if I want to do everything using code, for example last available version of Swift. And here I faced a problem cause there are no normal step-by-step guides which will explain how to do it. There are some information on Stack Overflow or on other sites but nothing really complete.
So, having a lot of knowledge from past how to do the same as I want on Windows using WinAPI, I started to dig into documentation and based on own mistakes start writing some code.
Here is first version of code:
import AppKit
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ notification: Notification) {
NSLog("Start app")
window = NSWindow(contentRect: NSMakeRect(0, 0, 1024, 768),
styleMask: [.closable, .titled],
backing: .buffered,
defer: false)
window.title = "Hey, new Window!"
window.center()
window.orderFrontRegardless()
}
func applicationWillTerminate(_ notification: Notification) {
NSLog("Terminate app")
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}
let app = NSApplication.shared
let appDelegate = AppDelegate()
app.delegate = appDelegate
app.run()
Basically we define AppDelegate
which implements several methods which define behavior that we need from application. applicationDidFinishLaunching
is called when application finished launch and simplified it is our WinMain
. applicationWillTerminate
- added only to be able to log fact when application is got closed. applicationShouldTerminateAfterLastWindowClosed
- additional configuration for application which result in automatic close of application (it means that event loop will be stopped) when last Window
object is closed, in our cases we have only one window, so when it will be closed at the same time whole application will be terminated.
This approach works and it seems like ideal solution for what we need, but the answer is - no. What is really needed is to be able to control event loop which is hidden here in run
function of the application. Here we want to have full control for the events and be able to do something between events handling.
So here comes second version -
import AppKit
var running = true
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
NSLog("start app")
}
func applicationWillTerminate(_ notification: Notification) {
NSLog("applicationWillTerminate")
running = false
}
}
class WindowDelegate: NSObject, NSWindowDelegate {
func windowDidResize(_ notification: Notification) {
NSLog("windowDidResize")
}
func windowWillClose(_ notification: Notification) {
NSLog("windowWillClose")
running = false
}
}
let app = NSApplication.shared
let appDelegate = AppDelegate()
app.delegate = appDelegate
app.setActivationPolicy(.regular)
app.finishLaunching()
let window = NSWindow(contentRect: NSMakeRect(0, 0, 1024, 768),
styleMask: [.closable, .titled, .resizable, .miniaturizable],
backing: .buffered,
defer: true)
let windowDelegate = WindowDelegate()
window.delegate = windowDelegate
window.title = "Hey, Window under control!"
window.acceptsMouseMovedEvents = true
window.center()
window.orderFrontRegardless()
while(running) {
var ev: NSEvent?
ev = app.nextEvent(matching: .any, until: nil, inMode: .default, dequeue: true)
if (ev != nil) {
NSLog("%@", ev!)
app.sendEvent(ev!)
}
}
app.terminate(nil)
Good! What do we have here? First, we have two classes AppDelegate
and WindowDelegate
- they help us to capture events from window and application in the same way as we normally do for this objects in Mac OS. Then we create application, window and wire with delegates. Then event loop starts until global variable running
is set to true basically we capture nextEvent
without any filtering and if there is event we send it to default handler for the application. As far as I understand events will be handled by application, application delegate, window and window delegate (probably order is wrong, need to find some clarification of how events are propagated). Solution can be simplified, we can remove all delegates and not use them but then handling of windowDidResize
for example will be non-trivial.
That’s it for today.