[email protected]
Your Profile
Dimas Ashidiqi
Sunday, 14 January 2024
#AppKit

How to Setup Xcode Project Programmatically with AppKit

A step by step on how to set up an AppKit project programmatically in Xcode

How to Setup Xcode Project Programmatically with AppKit

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. A screenshot of XCode where we can customize the project template

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.