How to Setup Xcode Project Programmatically with AppKit
A step by step on how to set up an AppKit project programmatically in Xcode
Introduction
When doing my UIs, I prefer to create all of it programmatically. I find it easier to maintain since you don’t have to jump back and forth between the storyboard and the code. It also prevents me from having merge conflict when working with teams.
Like all things in AppKit, a simple task like this can have a lot of gotchas. So I decided to write a step-by-step guide on how to do it.
Step-by-step guide
1. Create a new Xcode project
First, open Xcode and create a new project. Open “macOS” tab and select the App template. Then click Next. In the next screen, make sure to select Storyboard as the interface. Then choose where you want to save your project.
2. Delete the storyboard
Simply delete the storyboard file from the project navigator. When a pop-up shows, choose “Move to Trash”.
3. Delete the storyboard reference in the info.plist
Go to your project in the project navigator, from the Targets, open the Info tab.
At the Custom macOS Application Target Properties section, you can find a key called
Then delete the value for the key Main storyboard file base name (macOS)
.
4. Run your app manually
If you run your app now, nothing will be shown except for the app’s title in the
menu and the icon in the dock. That’s because your app instance is not run. So we will have
to run it manually. To do that, create a main.swift
file in your project. Then type the following code:
// main.swift
import AppKit
let app = NSApplication.shared
let appDelegate = AppDelegate()
app.delegate = appDelegate
app.run()
5. Delete the @main
attribute in your AppDelegate
class
After you do the previous step, XCode will yell at you with error 'main' attribute cannot be used in a module that contains top-level code
. To fix this, you can
delete the @main
attribute in your AppDelegate
class.
6. Create a window to be shown
Now, the correct instance of your app is run, but it will still not show anything because we haven’t asked it show anything.
So we need to create a new window and ask it to be shown.
Info
You don’t have to create a new window subclass and just create a new instance of NSWindow in the AppDelegate class. But I prefer to create a new subclass because I often also want the window to handle some shortcut events.
Create a new swift file named “MainWindow.swift” and type the following code:
// MainWindow.swift
class MainWindow: NSWindow {
init() {
super.init(
contentRect: NSRect(x: 0, y: 0, width: 970, height: 640),
styleMask: [
.titled,
.closable,
.miniaturizable,
.resizable,
.fullSizeContentView,
],
backing: .buffered,
defer: false
)
titlebarAppearsTransparent = true
if let screenFrame = NSScreen.main?.frame {
setFrame(screenFrame, display: true)
}
let vc = ViewController()
contentView = vc.view
contentViewController = vc
}
}
In this code, we create a new window subclass. The most important thing is at the end
of the init
method. We set the contentView
and contentViewController
to a ViewController
instance. If we don’t do this, the window will only show an empty background.
7. Create an instance of our window in the AppDelegate
Now that we have a window subclass, we can create an instance of it, then show it in the AppDelegate
’s
applicationDidFinishLaunching(_:)
method.
// AppDelegate.swift
class AppDelegate: NSObject, NSApplicationDelegate {
let mainWindow = MainWindow()
func applicationDidFinishLaunching(_ aNotification: Notification) {
mainWindow.makeKeyAndOrderFront(nil)
}
// ...
}
8. Customize the ViewController
Now, we already have a window that show our ViewController
, but when you run the app,
you will still see that the window is empty. That’s because by default, an NSViewController
will load its view from a
Nib
file. So we need to intercept this, and tell it to load the view from our code instead.
So go to your ViewController
class and add the following code:
// ViewController.swift
import Cocoa
class ViewController: NSViewController {
override func loadView() {
view = NSView()
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.red.cgColor
let label = NSTextField(labelWithString: "Hello World!")
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
// ...
}
And that’s it! Now when you run your app, you will see a red window with a label in the center.
Conclusion
I hope this guide can help you to set up your AppKit project programmatically. If you have any questions, feel free to ask me on X.