App structure: @main, App, Scene, WindowGroup
You will buildNothing new on the screen in this lesson. You find out what happens before ContentView appears and after the user leaves: the entry point, the scene, the phases an app moves through — and you give Trips a store that lives as long as the app does.
TimeAbout 75 minutes
WhyA UIKit app starts in an AppDelegate, gets a SceneDelegate, makes a window and sets its root view controller, and is then told about its life through a dozen callbacks. A SwiftUI app has one small file instead. In this lesson you read that file properly, watch it work in the console, and see where each thing you used to do in the delegates has gone. This is also the lesson where the most interview answers have changed in the last few years, because the app life cycle is no longer a set of callbacks: it is a value.
Open your Trips project as lesson 5 left it: double-click Trips.xcodeproj in Finder, or use File ▸ Open… (⌘O) in Xcode.
If you did not finish lesson 5, or would rather start from a known state, download Trips-lesson-05.zip instead. It is the project exactly as it stood at the end of lesson 5. Unzip it and open Trips.xcodeproj.
Run it (⌘R) to check that it builds. You should see where lesson 5 ended:

Click TripsApp.swift in the Project navigator. Xcode wrote it on lesson 5, and you have not needed it since:
import SwiftUI
@main
struct TripsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}Eleven lines, and they replace two delegate classes, a storyboard reference and a scene manifest. Read them against what you know:
| In UIKit | Here |
|---|---|
@UIApplicationMain on the AppDelegate class (later @main) | @main on a struct that conforms to App. It marks where the program starts. |
UIScene, UIWindowScene, and your SceneDelegate | body: some Scene. A scene is a piece of the interface whose life the system manages. |
Making a UIWindow and setting its rootViewController | WindowGroup { ContentView() }: a window, and the view at its root. |
application(_:didFinishLaunchingWithOptions:) | Mostly nothing. For work at launch, an init() in this struct. |
sceneDidBecomeActive, sceneWillResignActive, sceneDidEnterBackground | One value in the environment, scenePhase. Step 4. |
@main is not part of SwiftUI. It is a Swift attribute (5.3) that says: this type has a static main() method, and the program begins there. App provides that method for you. some Scene is the opaque type from lesson 3, and the braces of body are a builder block like lesson 4's — a scene builder this time.
The best way to learn what runs when is to make each piece say so. Give the app an initializer:
import SwiftUI@mainstruct TripsApp: App { init() { print("TripsApp.init") } var body: some Scene { WindowGroup { ContentView() } }}import SwiftUI
@main
struct TripsApp: App {
init() {
print("TripsApp.init")
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}And in ContentView.swift, have the view announce three things: that it has been created, that its body has run, and that it is on the screen.
struct ContentView: View { init() { print("ContentView.init") } var body: some View { let _ = print("ContentView.body") ScrollView { VStack { ForEach(Trip.samples) { trip in TripCard(trip: trip) } } } .onAppear { print("ContentView appeared") } }}struct ContentView: View {
init() {
print("ContentView.init")
}
var body: some View {
let _ = print("ContentView.body")
ScrollView {
VStack {
ForEach(Trip.samples) { trip in
TripCard(trip: trip)
}
}
}
.onAppear {
print("ContentView appeared")
}
}
}Run the app (⌘R). The screen is unchanged. Xcode's console shows

TripsApp.init
ContentView.init
ContentView.body
ContentView appearedThat is the launch of a SwiftUI app, in order. The App struct is created. Its body builds the scene, which creates the root view. SwiftUI asks the view for its body. Then the view appears. The line let _ = print(…) is the trick from lesson 4 for printing inside a builder block.
A UIKit app is told when it becomes active or goes to the background: methods are called on a delegate. A SwiftUI app reads it. The current phase is a value in the environment, and you can run code when it changes. Add two things to TripsApp.swift: the property that reads the phase, and an onChange on the scene.
import SwiftUI@mainstruct TripsApp: App { @Environment(\.scenePhase) private var scenePhase init() { print("TripsApp.init") } var body: some Scene { WindowGroup { ContentView() } .onChange(of: scenePhase) { old, new in print("\(old) -> \(new)") } }}import SwiftUI
@main
struct TripsApp: App {
@Environment(\.scenePhase) private var scenePhase
init() {
print("TripsApp.init")
}
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { old, new in
print("\(old) -> \(new)")
}
}
}@Environment(\.scenePhase) reads a value that the system keeps up to date; the environment has a lesson of its own (16). onChange(of:) calls its closure whenever the value changes, and hands it the old value and the new one. Run the app. Then:
In Device Hub, choose Controls ▸ Home — or click the Home button below the picture of the phone. The app goes away and the home screen appears.
Click the Trips icon on the home screen to come back.
Xcode's console, as the app starts

TripsApp.init
background -> inactive
ContentView.init
ContentView.body
ContentView appeared
inactive -> activeWhen you press Home:
active -> inactive
inactive -> backgroundAnd when you come back:
background -> inactive
inactive -> activeThere are three phases, and an app never jumps between the outer two. It always passes through inactive.
| Phase | What it means | UIKit said |
|---|---|---|
active | On the screen, and receiving touches. | sceneDidBecomeActive |
inactive | On the screen, or partly, but not receiving touches. | sceneWillResignActive |
background | Not on the screen. Apple: "expect the app to terminate soon after." | sceneDidEnterBackground |
inactive is easy to forget, because pressing Home passes through it in an instant. It lasts longer than that when something covers your app without replacing it. Run the app again, and in Device Hub drag downwards from the top-right corner of the phone's screen, to pull Control Center over the app.

The console adds one line:
active -> inactiveSwipe Control Center away again, and:
inactive -> activeNo background at all. The same happens for the notification shade, the app switcher, a system alert, an incoming call. So the rule for what to do in each phase: stop things that need the user's attention when you leave active — a game, a timer, a video. Save when you reach background.
So far the trips are a static list of samples that ContentView reads directly. A real app has one collection of trips that every screen shares, and that is saved when the app leaves. In SwiftUI that is an @Observable class: a class whose properties a screen can watch, so that a change to one redraws every view that read it. How it does that is lesson 16; for now the one word is enough. Make a new Swift File named TripStore, and add:
@Observable
final class TripStore {
var trips = Trip.samples
func save() {
print("Saving \(trips.count) trips") // a real save comes in Part 10
}
}The store has to be created once and kept for as long as the app runs. The App struct is the place for that. Give it the store as @State, hand it to the first view, and save when the app goes to the background:
import SwiftUI@mainstruct TripsApp: App { @Environment(\.scenePhase) private var scenePhase @State private var store = TripStore() init() { print("TripsApp.init") } var body: some Scene { WindowGroup { ContentView() ContentView(store: store) } .onChange(of: scenePhase) { old, new in print("\(old) -> \(new)") if new == .background { store.save() } } }}import SwiftUI
@main
struct TripsApp: App {
@Environment(\.scenePhase) private var scenePhase
@State private var store = TripStore()
init() {
print("TripsApp.init")
}
var body: some Scene {
WindowGroup {
ContentView(store: store)
}
.onChange(of: scenePhase) { old, new in
print("\(old) -> \(new)")
if new == .background {
store.save()
}
}
}
}ContentView now receives its trips instead of reaching for the samples:
struct ContentView: View { init() { let store: TripStore init(store: TripStore) { self.store = store print("ContentView.init") } var body: some View { let _ = print("ContentView.body") ScrollView { VStack { ForEach(Trip.samples) { trip in ForEach(store.trips) { trip in TripCard(trip: trip) } } } .onAppear { print("ContentView appeared") } }}struct ContentView: View {
let store: TripStore
init(store: TripStore) {
self.store = store
print("ContentView.init")
}
var body: some View {
let _ = print("ContentView.body")
ScrollView {
VStack {
ForEach(store.trips) { trip in
TripCard(trip: trip)
}
}
}
.onAppear {
print("ContentView appeared")
}
}
}Xcode reports an error — in the preview, as on lesson 5
missing argument for parameter 'store' in call
The preview creates a ContentView, so it needs a store too. It can simply make one:
#Preview { ContentView() ContentView(store: TripStore())}#Preview {
ContentView(store: TripStore())
}Run the app, press Home (Controls ▸ Home), and read the console:

active -> inactive
ContentView.init
inactive -> background
Saving 3 trips
ContentView.initThe save is there. So is something you did not ask for: ContentView.init, after each change of phase. Scroll up in the console and you will find that ContentView.body was printed once, when the app started, and never again.
Each change of phase makes SwiftUI evaluate the App's body again, because that body reads scenePhase. Evaluating it creates a new ContentView value — so the initializer runs — and then SwiftUI finds that nothing the view shows has changed, and does not ask for its body. The view struct is made over and over. The view on the screen is left alone. (In step 4 the struct was not even made again: there, the view was created without being given anything.) Hold on to this. It is the whole of the next lesson.
Three details in the code you changed.
| In the code | What it means |
|---|---|
@State private var store = TripStore() | The app owns one store. @State keeps it alive for as long as the app runs, and creates it exactly once — one of lesson 12's edge cases shows that this is new in Xcode 27. |
let store: TripStore in the view | No wrapper is needed to read an @Observable object. SwiftUI tracks which of its properties body reads, and runs body again when one of them changes. On lesson 16 you write that tracking by hand. |
if new == .background | The one moment the system promises before your app may be suspended or ended. There is no "will terminate" to rely on. |
On iPhone a WindowGroup is one window. On iPad and Mac the user can open several from the same group. Apple: "Every window in the group maintains independent state." So @State inside ContentView would exist once per window — and the store, which belongs to the App, is shared by all of them. That is the reason to put app-wide data here and not in the first view.
Some things still arrive only through a UIKit app delegate: the device token for push notifications is the usual example, and some third-party libraries ask for one. SwiftUI lets you attach a delegate without giving up App. Try it once, to see the order of events. Make a new Swift File named TripsDelegate, and replace everything in it:
import UIKit
final class TripsDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
print("TripsDelegate: did finish launching")
return true
}
}And in TripsApp.swift, one new property — the first line inside the struct:
import SwiftUI@mainstruct TripsApp: App { @UIApplicationDelegateAdaptor private var delegate: TripsDelegate @Environment(\.scenePhase) private var scenePhase @State private var store = TripStore() init() { print("TripsApp.init") } var body: some Scene { WindowGroup { ContentView(store: store) } .onChange(of: scenePhase) { old, new in print("\(old) -> \(new)") if new == .background { store.save() } } }}import SwiftUI
@main
struct TripsApp: App {
@UIApplicationDelegateAdaptor private var delegate: TripsDelegate
@Environment(\.scenePhase) private var scenePhase
@State private var store = TripStore()
init() {
print("TripsApp.init")
}
var body: some Scene {
WindowGroup {
ContentView(store: store)
}
.onChange(of: scenePhase) { old, new in
print("\(old) -> \(new)")
if new == .background {
store.save()
}
}
}
}Run the app. The console begins

TripsApp.init
TripsDelegate: did finish launching
background -> inactive
ContentView.init
ContentView.body
ContentView appeared
inactive -> active
ContentView.initYour App is created first. SwiftUI then creates your delegate and passes the launch on to it. Only after that does the scene, and with it the first view, come into being. You never create the delegate yourself, and UIApplication's own delegate is still SwiftUI's — yours is being forwarded to.
Apple's advice is to avoid this when you can: "Manage an app's life cycle events without using an app delegate whenever possible." Trips does not need one. Take it out again: delete TripsDelegate.swift (select it in the Project navigator, press the Delete key, and choose Move to Trash), and remove the property from TripsApp.swift:
import SwiftUI@mainstruct TripsApp: App { @UIApplicationDelegateAdaptor private var delegate: TripsDelegate @Environment(\.scenePhase) private var scenePhase @State private var store = TripStore() init() { print("TripsApp.init") } var body: some Scene { WindowGroup { ContentView(store: store) } .onChange(of: scenePhase) { old, new in print("\(old) -> \(new)") if new == .background { store.save() } } }}import SwiftUI
@main
struct TripsApp: App {
@Environment(\.scenePhase) private var scenePhase
@State private var store = TripStore()
init() {
print("TripsApp.init")
}
var body: some Scene {
WindowGroup {
ContentView(store: store)
}
.onChange(of: scenePhase) { old, new in
print("\(old) -> \(new)")
if new == .background {
store.save()
}
}
}
}A program has one entry point. See what the compiler says about a second. Add this at the bottom of TripsApp.swift:
@main
struct OtherApp: App {
var body: some Scene {
WindowGroup {
Text("Another way in")
}
}
}It does not compile. Xcode reports, on both structs
'main' attribute can only apply to one type in a module
Delete the second struct again.
@mainstruct OtherApp: App { var body: some Scene { WindowGroup { Text("Another way in") } }}The prints have done their work. Leave the app as it should be: the store, and the save. In TripsApp.swift:
import SwiftUI@mainstruct TripsApp: App { @Environment(\.scenePhase) private var scenePhase @State private var store = TripStore() init() { print("TripsApp.init") } var body: some Scene { WindowGroup { ContentView(store: store) } .onChange(of: scenePhase) { old, new in print("\(old) -> \(new)") .onChange(of: scenePhase) { _, new in if new == .background { store.save() } } }}import SwiftUI
@main
struct TripsApp: App {
@Environment(\.scenePhase) private var scenePhase
@State private var store = TripStore()
var body: some Scene {
WindowGroup {
ContentView(store: store)
}
.onChange(of: scenePhase) { _, new in
if new == .background {
store.save()
}
}
}
}{ _, new in ignores the old value, which the code no longer uses. And in ContentView.swift:
struct ContentView: View { let store: TripStore init(store: TripStore) { self.store = store print("ContentView.init") } var body: some View { let _ = print("ContentView.body") ScrollView { VStack { ForEach(store.trips) { trip in TripCard(trip: trip) } } } .onAppear { print("ContentView appeared") } }}struct ContentView: View {
let store: TripStore
var body: some View {
ScrollView {
VStack {
ForEach(store.trips) { trip in
TripCard(trip: trip)
}
}
}
}
}Without an init of your own, Swift writes one for the struct that takes the store, so ContentView(store: store) still works. Run it one last time and press Home:
Run it (⌘R). The simulator shows

And in the console, when you press Home, one line:
Saving 3 tripsTripStore, an @Observable store owned by the App struct and handed to the first view.Next: the puzzle from step 6. ContentView.init ran again and again, and ContentView.body only once. What a view really is, when body runs, and why none of your setup code belongs in a view's init.
Stuck, or want to compare with your own? This is the project as it stands at the end of this lesson, built and run before it was put here.
Trips.xcodeproj in Xcode.13 cards for this lesson, each labelled with its topic. 7 ask how you would write something; the other 6 are trick questions, written to catch a misunderstanding. Answer in your head first, then tap the card.
ContentView.
A struct that conforms to App, marked @main. Its body returns scenes; WindowGroup is the scene for an ordinary window, and its braces hold the root view.

Give the App struct an init(). It is the earliest of your own code to run: it printed starting before the first view was created.

Read \.scenePhase from the environment, and attach onChange(of: scenePhase) to the scene. The phase is .active, .inactive or .background.

A view can read \.scenePhase too. onChange also has a form whose closure takes no parameters; inside it, the property already holds the new value.

@State in the App struct. The app owns the object; every window made from the WindowGroup is given the same one.

@UIApplicationDelegateAdaptor, with the delegate's type. SwiftUI creates the delegate and forwards the app's callbacks to it. Declare it once, in the App struct only.

onChange(of:) { old, new in … }. It works for any Equatable value, on a view or on a scene. Add initial: true to run it once at the start as well.
@main?No. 'main' attribute can only apply to one type in a module A program starts in exactly one place.
active to background?
No. It passes through inactive. After the launch, pressing Home printed active -> inactive and then inactive -> background. Coming back is the same in reverse.
No. It becomes inactive and stays there until Control Center is put away; then it is active again. The same goes for the notification shade, the app switcher and system alerts. background means the app is no longer on the screen at all.
@UIApplicationDelegateAdaptor, which runs first: the App struct's init, or the delegate's didFinishLaunching?
The App's init. It printed App.init, then delegate: did finish launching. SwiftUI creates your App, then the delegate, and then tells the delegate about the launch.
ContentView has a @State property. Do the two windows share its value?No. Each window made from a WindowGroup gets its own copy of the view hierarchy, and its own storage for every @State in it. Apple: "Every window in the group maintains independent state." Data that all windows should share belongs to the App struct, or to a model it owns.
AppDelegate and SceneDelegate are gone from the project. Are they gone from the running app?
No. A SwiftUI app is still a UIKit app underneath: UIApplication has a delegate, and each scene has one. SwiftUI supplies them. Asked for their types, the running app printed SwiftUI.AppDelegate and SwiftUI.AppSceneDelegate.
9 questions of the kind an interviewer uses to find out whether you really know this — the corners, the traps, and the things that changed recently. Each has its source. Wherever the answer is what some code does, that code was compiled and run, and what you see under “Show the answer” is what it really did. Say your answer aloud before you open it.
The app and its first view both print every change of scenePhase. The app is launched, and nothing else happens. What is printed?

The console shows
app: background -> inactive
app: inactive -> active
view: inactive -> activeThree lines, and the first surprises most people: the app's phase starts at background. A launch is a move from the background to the foreground, by way of inactive.
The view prints only the last step. By the time the view exists and is watching, the scene is already inactive, so the first change it can see is the one to active.
Two practical points. onChange on the scene fires during launch, so code there that means "the user came back" also runs when the user arrives for the first time — which is usually what you want, but know it. And the phase background at launch is not a quirk of the simulator: an app can be launched into the background by the system, for a background task or a notification, and then it never becomes active at all.
Where this comes from: ScenePhase · App organization — Apple: "The system moves your app’s Scene instances through phases that reflect a scene’s operational state."
The same app. The user presses Home, then taps the icon to return. What is printed for each — and which line is your last dependable chance to save?

Pressing Home printed:
app: active -> inactive
view: active -> inactive
app: inactive -> background
view: inactive -> backgroundAnd coming back:
app: background -> inactive
view: background -> inactive
app: inactive -> active
view: inactive -> activeAlways through inactive, in both directions, and the app is told before the view each time.
The line to save on is inactive -> background. Apple's documentation is blunt about what follows: "When an app enters the background phase, expect the app to terminate soon after." There is no later moment you can count on. A suspended app that the system removes from memory is not told anything, and SwiftUI has no equivalent of applicationWillTerminate.
Do not save on active -> inactive instead. That fires for Control Center, the app switcher and every system alert, so you would be saving constantly, and for the wrong reason.
Where this comes from: ScenePhase — Apple, ScenePhase: the app-level value shows background when all scenes are in the background; "you can use that opportunity to free any resources".
scenePhase can be read in the App struct and in any view. On an iPad with two windows of your app side by side, the user taps in one of them. Do the two places report the same thing?
No, and the difference is documented. Read inside a view, scenePhase is the phase of the scene that contains that view. Read in the App, it is a summary of all scenes: Apple says it is active "if any scene is active" and inactive "when no scenes are active".
So with two windows, each view's value follows its own window, while the app's value stays active as long as either is. It reaches background only when every window has.
That tells you where each kind of work belongs. Work that concerns one window — pausing its video, stopping its timer — reads the phase in that window's views. Work that concerns the whole app — saving the store — reads it in the App. On an iPhone, with its one window, the two always agree, which is why code that confuses them survives until someone opens a second window on an iPad.
Where this comes from: ScenePhase · WindowGroup — Apple, ScenePhase: "How you interpret the value depends on where it’s read from."
A game pauses itself when the phase becomes background. A player pulls down Control Center to change the volume, and loses a life while it is open. What went wrong?
Control Center does not send an app to the background. With it pulled down over the app, the running app printed only this:
active -> inactive
inactive, not in the backgroundScreenshot · iPhone 17, iOS 27.0And when it was swiped away:
inactive -> activeThe app was still on the screen, behind the panel, and still running. It was simply not receiving touches. That is what inactive means, and it is the phase the game should have paused on.
The rule: anything that needs the user's attention stops when the phase is no longer active. Saving, and giving up resources, wait for background.
Where this comes from: ScenePhase — Apple describes inactive as a scene that is in the foreground but "should pause its work".
A colleague adds a file named main.swift to the app, with one line in it, to "run something first". The project stops compiling. Why?
It does not compile. Xcode reports
'main' attribute cannot be used in a module that contains top-level code
'main' attribute cannot be used in a module that contains top-level code Swift has two ways to say where a program starts, and a module may use only one. Code written at the top level of a file named main.swift is the old way: the program starts at its first line. @main on a type is the new way: the program starts by calling that type's static main().
The proposal that introduced @main spells out the corner your colleague hit: "A main.swift file is always considered to be an entry point, even if it has no top-level code." So even an empty file with that name is enough to break the build.
Rename the file, and put the line where code that must run first goes in a SwiftUI app: the init() of the App struct.
Where this comes from: SE-0281: @main: Type-Based Program Entry Points · Hacking with Swift interview question: What does the @main attribute do? — SE-0281, “@main: Type-Based Program Entry Points” (Swift 5.3).
@main is usually written on the App struct. Here it is on an enum that is not an App at all. Does this compile — and what is it for?

The console shows
starting TripsIt compiles, and it ran: the console shows which branch was taken. @main asks for one thing only — a type with a static method called main(). Hacking with Swift's interview answer puts it this way: with @main you "need to implement a main() method to handle setting up your program, but if you're using SwiftUI the App protocol provides that for you".
Because App gives every conforming type a main(), you can call it yourself, and that makes this pattern possible. Unit tests for an app run inside the app, so by default every test run starts the whole thing: the real store, the real network calls, the real first screen. A launcher like this one starts a bare stand-in app when it sees that tests are loaded, and the tests run faster and touch nothing.
If the type has no main(), the compiler tells you exactly what it wants: a static function of type () -> Void, or the throwing or async forms of it.
Where this comes from: Hacking with Swift interview question: What does the @main attribute do? · SE-0281: @main: Type-Based Program Entry Points · App — Apple, App: "The protocol provides a default implementation of the main() method that the system calls to launch your app."
A tutorial from 2022 reacts to the scene phase like this. What does Xcode 27 say about it?

It compiles, with a warning
'onChange(of:perform:)' was deprecated in iOS 17.0: Use `onChange` with a two or zero parameter action closure instead.
It compiles, with a warning: 'onChange(of:perform:)' was deprecated in iOS 17.0: Use onChange with a two or zero parameter action closure instead.
The first onChange passed its closure one value, the new one. That form has been deprecated since iOS 17. The replacements take either two parameters, the old value and the new, or none at all:

The difference is more than the parameter count, which is why Apple did not simply add a parameter. The old closure ran before the view was updated, so reading the property inside it gave the old value while the parameter held the new one. The new forms run after the update: inside the closure, the property and new agree. Code that relied on the old timing has to be read again when you convert it. And onChange(of:initial:) with initial: true also runs once when the view first appears, which the old form could not do.
Where this comes from: onChange(of:initial:_:) — The compiler's own deprecation message, and Apple's reference for onChange(of:initial:_:).
ContentView keeps the selected trip in @State. The App keeps the store in @State. On an iPad the user opens a second window of the app. How many selections are there now, and how many stores?
Two selections, one store. A WindowGroup is a template: each window the user opens is a fresh copy of the view hierarchy inside its braces. Apple: "Every window in the group maintains independent state. For example, the system allocates new storage for any State or StateObject variables instantiated by the scene's view hierarchy for each window that it creates."
The App struct is outside that template. There is one of it, so there is one store, and both windows are handed the same object. Add a trip in one window and it appears in the other; select a trip in one and the other does not move. That is nearly always the behavior you want, and it falls out of putting each piece of state at the right level.
One more fact for the interview. An iPad app only offers a second window if it says it supports them: the Info.plist key is UIApplicationSupportsMultipleScenes, inside UIApplicationSceneManifest. A new Xcode project sets it for you.
Where this comes from: WindowGroup · Scene — Apple, WindowGroup.
In UIKit, set-up code went in application(_:didFinishLaunchingWithOptions:). An interviewer asks where it goes in a SwiftUI app — and whether the app delegate has really gone.

The console shows
1. the App struct is created
2. the delegate hears that launching has finished
UIApplication's delegate is SwiftUI.AppDelegate
3. the first view appearsMost of it goes in the init() of the App struct, which runs first of all — the console above shows the order. Work that belongs to a model goes in that model, created by the App as @State.
The delegate has not gone; it has been hidden. The running app says that UIApplication's delegate is a class of SwiftUI's own. When you need to receive something that only a delegate is sent — the push-notification token, a background URL session's events, a callback some SDK insists on — you write a delegate class and attach it with @UIApplicationDelegateAdaptor. SwiftUI creates it and forwards to it.
Three rules come with the adaptor, all from Apple's documentation. Declare it only in the App, and only once: a second one is "a runtime error". Do not create the delegate yourself. And prefer not to need it: "Manage an app's life cycle events without using an app delegate whenever possible. For example, prefer to handle changes in ScenePhase instead of relying on delegate callbacks."
Where this comes from: UIApplicationDelegateAdaptor · App — Apple, UIApplicationDelegateAdaptor.