avatar
Published on

Adding an App Delegate to a SwiftUI App

Authors
  • avatar
    Name
    Mick MacCallum
    Twitter
    @0x7fs

SwiftUI's App protocol provides a modern way to structure your app, but sometimes you need access to UIKit's App Delegate for handling app lifecycle events, push notifications, deep links, or other system callbacks. This article shows you how to integrate an App Delegate into your SwiftUI app.

Why Use App Delegate in SwiftUI?

While SwiftUI's App protocol handles basic app lifecycle, there are scenarios where you need the App Delegate:

  • Push Notifications: Registering for remote notifications and handling token updates
  • Deep Links: Processing URL schemes and universal links
  • Background App Refresh: Managing background tasks and app state
  • Third-party SDKs: Many libraries still require App Delegate integration (usually for initialization in the application(_:didFinishLaunchingWithOptions:) method)
  • System Callbacks: Handling memory warnings, state restoration, etc.

Basic App Delegate Integration

The key is using the @UIApplicationDelegateAdaptor property wrapper in your SwiftUI app to obtain a reference to an instance of the App Delegate.

import SwiftUI
import UIKit

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        // Configure your app here
        print("App launched")
        return true
    }
    
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        // Handle push notification registration
        let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
        print("Device token: \(tokenString)")
    }
    
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Failed to register for notifications: \(error)")
    }
}

Scene Delegate Integration (Bonus)

For more complex apps that need scene-based lifecycle management, it is also necessary to add an App Delegate as a prerequisite for the Scene Delegate. Implment your App Delegate with the configurationForConnecting method to return a UISceneConfiguration with the delegateClass set to the Scene Delegate class.

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .onChange(of: scenePhase) { oldPhase, newPhase in
            switch newPhase {
            case .active:
                print("App became active")
            case .inactive:
                print("App became inactive")
            case .background:
                print("App entered background")
            @unknown default:
                break
            }
        }
    }
}

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        let sceneConfig = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
        sceneConfig.delegateClass = SceneDelegate.self
        return sceneConfig
    }
}

class SceneDelegate: NSObject, UIWindowSceneDelegate {
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Handle scene connection
        print("Scene will connect")
    }
    
    func sceneDidDisconnect(_ scene: UIScene) {
        print("Scene did disconnect")
    }
    
    func sceneDidBecomeActive(_ scene: UIScene) {
        print("Scene became active")
    }
    
    func sceneWillResignActive(_ scene: UIScene) {
        print("Scene will resign active")
    }
    
    func sceneWillEnterForeground(_ scene: UIScene) {
        print("Scene will enter foreground")
    }
    
    func sceneDidEnterBackground(_ scene: UIScene) {
        print("Scene did enter background")
    }
}

Best Practices

  1. Keep it minimal: Only implement the App Delegate methods you actually need.
  2. Use SwiftUI when possible: Prefer SwiftUI's lifecycle methods for simple cases
  3. Separate concerns: Create dedicated classes for different responsibilities (notifications, deep links, etc.)
  4. Test thoroughly: App Delegate methods are critical for app functionality