Creating an AppKit App Without the Storyboard

There are many reasons to want to build an AppKit app without a storyboard. Usually the largest advantage is that in a team environment, storyboards can be a nightmare for version control. It’s much easier to resolve conflicts in code than in a .storyboard XML file. Of course you can use SwiftUI, but there are also reasons for wanting to avoid using SwiftUI as your base as of 2023.

Prerequisites

We’re going to assume you’re running on Xcode 14+.

Create your project

Create your Xcode project in AppKit, selecting Storyboard initially – don’t worry, we will remove in a moment.

Delete the Main.storyboard

Make sure to move it to the trash.

Update your target settings

In your main target, delete the AppKit Main Storyboard File Base Name

Preview Image

Create your main.swift file

At the root of the project, create a main.swift file.

import Cocoa

let appDelegate = AppDelegate()
NSApplication.shared.delegate = appDelegate
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)

Remove the @main notation on the AppDelegate

Remove the @main attribute before your AppDelegate:

@main
class AppDelegate: NSObject, NSApplicationDelegate {
    // rest of code
}

Setup your first ViewController from the App Delegate

class MyCustomViewController: NSViewController {
    override func loadView() {
        self.view = NSView()
        self.view.frame = NSRect(x: 0, y: 0, width: 200, height: 200)
        self.view.wantsLayer = true
        self.view.layer?.backgroundColor = NSColor.red.cgColor
    }
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
    // Create a window
    window = NSWindow(
        contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
        styleMask: [.titled, .closable, .resizable, .fullSizeContentView],
        backing: .buffered, defer: false)
    
    window.center()
    window.title = "No Storyboards Here"
    
    // Create an instance of your view controller
    let myViewController = MyCustomViewController()
    
    // Set the window's content view to the view controller's view
    window.contentView = myViewController.view
    
    window.makeKeyAndOrderFront(nil)
}