SwiftUI
iOS14
AppDelegate
app life cycle
iOS development

SwiftUI app life cycle iOS14 where to put AppDelegate code?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

With the iOS 14 SwiftUI app lifecycle, many developers wonder where legacy AppDelegate code should live. The answer is that SwiftUI’s @main App is now the primary entry point, but you can still bridge UIKit delegate behaviors when needed. Push notifications, analytics SDK initialization, deep link handling, and background task registration may still require delegate hooks. A clean architecture keeps lifecycle responsibilities explicit and avoids scattering startup logic across views.

Core Sections

1. Use @main App as primary entry

swift
1import SwiftUI
2
3@main
4struct MyApp: App {
5    var body: some Scene {
6        WindowGroup {
7            RootView()
8        }
9    }
10}

Basic startup state should be initialized here or injected via environment objects.

2. Bridge AppDelegate with adaptor

For APIs that still require UIApplicationDelegate:

swift
1import SwiftUI
2
3class AppDelegate: NSObject, UIApplicationDelegate {
4    func application(_ application: UIApplication,
5                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
6        // SDK setup, push registration, etc.
7        return true
8    }
9}
10
11@main
12struct MyApp: App {
13    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
14
15    var body: some Scene {
16        WindowGroup { RootView() }
17    }
18}

This is the standard migration path for mixed SwiftUI/UIKit lifecycle logic.

3. Scene-level handlers

Use SwiftUI scene handlers for phase-based behavior:

swift
1@Environment(\.scenePhase) private var scenePhase
2
3.onChange(of: scenePhase) { phase in
4    if phase == .background {
5        // persist state
6    }
7}

This often replaces older foreground/background delegate methods.

Keep external-event entry points in delegate/adaptor, then forward into an app-level router/service. This prevents lifecycle APIs from leaking into view code.

5. Testing and modularity

Isolate startup integrations (analytics, crash reporting, remote config) behind protocols. This makes app launch behavior testable and avoids tight coupling to AppDelegate.

6. Migration guidance

During incremental migration:

  • keep unavoidable UIKit delegate methods in adaptor
  • move app state and navigation logic into SwiftUI layers
  • gradually reduce direct delegate dependencies

This yields cleaner long-term architecture.

Validation and production readiness

A reliable implementation is not complete until it is validated under realistic conditions. Add a minimal but representative test matrix that includes normal inputs, edge cases, and malformed data. For UI-focused topics, include at least one scenario for lifecycle or timing behavior (initial load, state transition, and cleanup) so regressions are detected when framework versions change. For infrastructure and tooling topics, run commands against a disposable environment before applying in production and capture expected outputs in documentation. This reduces ambiguity when teammates reproduce steps later.

Instrumentation is equally important. Add structured logs around the critical path, including input shape, selected branch decisions, and failure reasons. Keep logs concise and machine-parseable so alerts and dashboards can surface patterns quickly. If operations are expensive or remote (network, filesystem, container orchestration), include timeout handling and explicit retry policy with backoff. Silent retries without bounds are a common source of hidden incidents.

Finally, document assumptions and compatibility boundaries near the code or article examples: runtime versions, platform requirements, and known behavior differences across environments. Add a lightweight checklist for rollouts that covers dependency pinning, backup/rollback strategy, and smoke checks after deployment. Teams that treat these steps as part of the baseline implementation, not optional polish, usually see fewer production surprises and faster recovery when issues occur.

Common Pitfalls

  • Duplicating startup logic in both @main App and AppDelegate.
  • Putting global service initialization inside random views.
  • Mixing scene phase handling and delegate callbacks without ownership boundaries.
  • Keeping UIKit-era singleton patterns where SwiftUI environment injection is better.
  • Ignoring testability of launch-time side effects.

Summary

In iOS 14+ SwiftUI lifecycle, @main App is the main entry point, and @UIApplicationDelegateAdaptor is the right bridge for APIs that still require AppDelegate hooks. Keep lifecycle responsibilities separated: delegate for platform callbacks, SwiftUI for state and UI flow. This structure supports clean migration and maintainable app startup behavior.


Course illustration
Course illustration

All Rights Reserved.