Start a macOS app

Read the App and scene structure

Understand how the SwiftUI App entry point creates scenes and how WindowGroup supplies the application’s primary windows.

Open the app’s entry point file. Everything the app does starts here, so let’s read it slowly.

@main
struct NotesApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

@main tells Swift this type is the program’s entry point. The struct conforms to the App protocol, which asks for one thing: a body that returns a scene.

What a scene is

A scene is a piece of your app’s interface that macOS manages for you. You declare what it contains. The system decides how to present it, restore it, and tear it down.

This split is the core idea of SwiftUI app structure. You describe, macOS manages.

WindowGroup is a family of windows

WindowGroup is the most common scene. It does not describe a single window. It describes a family of windows that all share the same root view.

On macOS you can see this right away. Run the app and press Cmd+N, or choose File → New Window. You get a second, independent window showing another ContentView. That’s WindowGroup doing its job.

This is a real difference from iOS. On the iPhone your app is one full-screen scene. On the Mac, users expect to open three windows of your app side by side, and WindowGroup gives you that for free. The only condition is that your state can handle it. Each window gets its own copy of view-local @State.

Give windows a starting size

You can shape the windows the scene creates. This gives new windows a sensible starting size while keeping them resizable:

WindowGroup {
  ContentView()
}
.defaultSize(width: 800, height: 500)

Without it, macOS picks a size for you, and it’s rarely the one you want.

Keep startup work out of body

SwiftUI can evaluate body more than once. Anything with side effects does not belong there: opening files, starting timers, spawning processes.

Create your services explicitly, store them as properties on the app struct, and pass them down to the views that need them. We’ll do exactly that in the next lesson with the notes model.

The mistake to watch for is doing setup inside body and assuming it runs exactly once. It does not. If you see duplicated log lines or two network requests at launch, this assumption is almost always the cause. Move the work into a model object you create once and inject.

Lesson completed