Logo for tanaschita.com

Understanding the difference between AppDelegate, SceneDelegate and SwiftUI's App protocol

Learn about the life cycle of a SwiftUI application.

04 Apr 2022 · 7 min read

Since SwiftUI was released, new mechanisms were introduced that help us to handle the life cycle of the app and to manage specific interactions with the iOS system.

With UIApplicationDelegate, UIWindowSceneDelegate and SwiftUI's App type now being around, it may be confusing to decide which one to use when, especially when implementing features like push notifications, home screen actions etc.

Let's start with the oldest one.

Sponsorship logo
Preparing for a technical iOS job interview
Check out my new book on preparing for a technical iOS job interview with over 200 questions & answers. Test your knowledge on iOS topics such as Swift & Objective-C, SwiftUI & UIKit, Combine, HTTP Networking, Authentication, Core Data, Concurrency with async/await, Security, Automated Testing and more.
LEARN MORE

AppDelegate

Prior to iOS 13, only UIKit's UIApplicationDelegate existed. It was the main entry point of the app and the center of life cycle events where app launch, foreground, background modes etc. were handled.

The following code shows a few typical methods the AppDelegate can contain and handle.

class AppDelegate: NSObject, UIApplicationDelegate {
var window: UIWindow?
// MARK: Initializing the application
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// The launch process is almost done and the app is almost ready to run.
let window = UIWindow(frame: UIScreen.main.bounds)
window.makeKeyAndVisible()
window.rootViewController = RootViewController()
self.window = window
return true
}
// MARK: Life cycle
func applicationDidEnterBackground(_ application: UIApplication) {
// The app is now in the background.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// The app is about to enter the foreground.
}
...
// MARK: Registrating remote notifications
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// The app successfully registered with Apple Push Notification service.
}
...
}

The code above only shows a small excerpt of the AppDelegate. It offers many more delegate methods, for example to handle environment changes like memory or time, downloading data in the background, handling quick actions, interacting with frameworks like WatchKit or HealthKit, handling universal links and more.

SceneDelegate

Prior to iOS 13, we configured a UIWindow object in AppDelegate's application(_:didFinishLaunchingWithOptions:) -> Bool method by assigning a UIViewController instance as its root view controller.

Starting with iOS 13, Apple introduced so called scenes to support multiple windows on the iPad. For that, some responsibilities of the AppDelegate were outsourced to the SceneDelegate.

To adapt our code to the new scene based approach, the AppDelegate gets two additional methods as shown below.

class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Retrieves the configuration data for UIKit when creating a new scene.
let configuration = UISceneConfiguration(name: connectingSceneSession.configuration.name, sessionRole: connectingSceneSession.role
)
configuration.delegateClass = SceneDelegate.self
return configuration
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// User closed one or more scenes.
}
}

By assigning SceneDelegate.self to the configuration in the scene(_:willConnectTo:options:) method, we outsource the window management to the SceneDelegate which might look as follows:

class SceneDelegate: NSObject, UISceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// A new scene was added to the app.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIViewController()
window.makeKeyAndVisible()
}
}
func sceneDidEnterBackground(_ scene: UIScene) {
// A scene did enter the background.
}
func sceneWillEnterForeground(_ scene: UIScene) {
// A scene is about to enter the foreground.
}
}

As we can see above, scenes have almost the same lifecycle methods as the AppDelegate. The main difference is that with the scene based approach, multiple scenes can be created where each has its own life cycle.

SwiftUI's App protocol

Starting with iOS 14, Apple introduced the App protocol for pure SwiftUI applications which mostly replaced the AppDelegate and SceneDelegate. A most basic SwiftUI App implements the body property as the entry point of a SwiftUI application.

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

Before that, we would have used a UIHostingViewController initialized with a SwiftUI view in the AppDelegate or SceneDelegate.

The body property is of type some Scene we recognize from the scene based approach introduced in iOS 13. The returned WindowGroup is a Scene which wraps SwiftUI views.

To observe scene life cycle changes we know from SceneDelegate's methods, we can add an environment property scenePhase and subscribe to any changes.

struct ExampleApp: App {
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { (newScenePhase) in
switch newScenePhase {
case .active:
// The scene is in the foreground and interactive.
case .inactive:
// The scene is in the foreground but should pause its work.
case .background:
// The scene isn’t currently visible in the UI.
@unknown default:
break
}
}
}
}

For now, we still may need the AppDelegate or SceneDelegate in some cases, for example to register for remote push notifications or to add quick home screen actions. In those cases, we can use the UIApplicationDelegateAdaptor property wrapper.

struct ExampleApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
}

In doing so, SwiftUI instantiates the AppDelegate and calls the delegate’s methods in response to life cycle events or communications with the iOS system as we know it from UIKit.

Sponsorship logo
Preparing for a technical iOS job interview
Check out my new book on preparing for a technical iOS job interview with over 200 questions & answers. Test your knowledge on iOS topics such as Swift & Objective-C, SwiftUI & UIKit, Combine, HTTP Networking, Authentication, Core Data, Concurrency with async/await, Security, Automated Testing and more.
LEARN MORE

Newsletter

Image of a reading marmot
Subscribe

Like to support my work?

Say hi

Related tags

Articles with related topics

architecture

ios

swift

Quick guide on home screen quick actions for iOS

Learn how to give users access to your app's functionality directly from the home screen.

18 Apr 2022 · 4 min read

Latest articles and tips

© 2023 tanaschita.com

Privacy policy

Impressum