Once I faced 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 by hand, for example last by writing some code using latest available version of Swift. And here I faced a problem - there are no simple step-by-step guide 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 on Windows using WinAPI, I started to dig into documentation and write some code.
Here is first version:
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
applicationWillTerminate
added 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 case 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 looks like it is an ideal solution for what we need, but actually - no. What is really needed is to to control event loop which is hidden here in run
function of the application. 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. After that event loop starts until global variable running
is set, basically we capture nextEvent
without any filtering and when new event occur 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
will become not so trivial.
That’s it for today.
>> Home