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.
12 minute lesson
Open the app’s entry point file. Everything the app does starts here, so it is worth reading 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 requires one thing: a body that returns a scene.
A scene is a piece of your app’s interface that macOS manages for you. You declare what it contains, and 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 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 this has a visible consequence: run the app and press Cmd+N, or choose File → New Window. You get a second, independent window showing another ContentView. That is 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 behavior for free — as long as your state can handle it. Each window gets its own copy of view-local @State.
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)
Keep startup work out of body. SwiftUI can evaluate body more than once, so anything with side effects — opening files, starting timers, spawning processes — does not belong there. Create services explicitly, store them as properties on the app struct, and inject them into the views that need them.
The mistake to watch for: doing setup inside body and assuming it runs exactly once. It does not. If you see duplicated log lines or double network requests at launch, this assumption is usually the cause. Move the work into a model object you create once and pass down.
Lesson completed