The Ultimate Guide to the SwiftUI 2 Application Life Cycle

Goodbye AppDelegate

Peter Friese
Oct 21, 2020 · 6 min read
An illustration of a rocket circling around the Earth.
An illustration of a rocket circling around the Earth.
Image based on Rocket by Icongeek26 on The Noun Project

For the longest time, iOS developers have used AppDelegate as the main entry point for their applications. With the launch of SwiftUI2 at WWDC 2020, Apple has introduced a new application life cycle that (almost) completely does away with AppDelegate, making way for a DSL-like approach.

In this article, I’ll discuss why this change was introduced and how you can make use of the new life cycle in new or existing apps.

Specifying the Application Entry Point

One of the first questions we need to answer is “How can we tell the compiler about the entry point to our application?” SE-0281 specifies how type-based program entry points work:

“The Swift compiler will recognize a type annotated with the @main attribute as providing the entry point for a program. Types marked with @main have a single implicit requirement: declaring a static main() method.”

When creating a new SwiftUI app, the app’s @main class looks like this:

So where is the static main() function that’s mentioned in SE-0281?

Well, it turns out that framework providers can (and should) provide a default implementation for their users’ convenience. Looking at the code snippet above, you’ll notice that SwiftUIAppLifeCycleApp conforms to the App protocol. Apple provides a protocol extension that looks like this:

And there we have it — this protocol extension provides a default implementation that takes care of the application startup.

Since the SwiftUI framework isn’t open source, we can’t see how Apple implemented this, but Swift Argument Parser is open source and uses this approach as well. Check out the source code for ParsableCommand to see how they use a protocol extension to provide a default implementation of the static main function that serves as the program entry point:

If all this sounds a bit complicated, the good news is you don’t actually have to worry about it when creating a new SwiftUI application: Just make sure to select “SwiftUI App” in the Life Cycle drop-down when creating your app, and you’re done:

A screenshot of the author creating a new SwiftUI project. His project is called My Nifty Swift UI 2 App
A screenshot of the author creating a new SwiftUI project. His project is called My Nifty Swift UI 2 App
Creating a new SwiftUI project

Let’s take a look at some common scenarios.

Initialising Resources/Your Favourite SDK or Framework

Most applications need to perform a few steps at application startup: fetching some configuration values, connecting to a database, or initialising a framework or third-party SDK.

Usually, you’d do this in your ApplicationDelegate’s application(_:didFinishLaunchingWithOptions:) method. As we no longer have an application delegate, we need to find other ways to initialise our application. Depending on your specific requirements, here are some strategies:

  • Implement an initialiser on your main class (see the docs)
  • Set initial values for stored properties (see the docs)
  • Set default property values using a closure (see the docs)

If none of this meets your needs, you might need an ApplicationDelegate after all. Read on until the end to learn how you can add one.

Handling Your Application’s Life Cycle

It’s sometimes useful to be able to know which state your application is in. For example, you might want to fetch new data as soon as your app becomes active or flush any caches once your application becomes inactive and transitions into the background.

Usually, you’d implement applicationDidBecomeActive, applicationWillResignActive, or applicationDidEnterBackground on your ApplicationDelegate.

Starting with iOS 14.0, Apple has provided a new API that allows for a more elegant and maintainable way of tracking an app’s state: ScenePhase. Your project can have multiple scenes, but chances are you’ve got only one scene, represented by WindowGroup.

SwiftUI tracks a scene’s state in the environment, and you can make the current value accessible to your code by fetching it using the @Environment property wrapper and then using the onChange(of:) modifier to listen to any changes:

It’s worth noting that you can read the phase from other locations in your app as well. When reading the phase at the top level of the app (like shown in the code snippet), you’ll get an aggregate of all of the phases in your app. A value of .inactive means that none of the scenes in your app are active.

When reading the phase on a view, you’ll receive the value of the phase that contains the view. Keep in mind your app might contain other scenes that have other phase values at this time. For more details about scene phases, read Apple’s documentation.

Handling Deep Links

Previously, when handling deep links, you’d have to implement application(_:open:options:) and route the incoming URL to the most appropriate handler.

This becomes a lot easier with the new app–life cycle model. You can handle incoming URLs by attaching the onOpenURL modifier to the top-most scene in your app:

What’s really cool: You can install multiple URL handlers throughout your application — making deep linking a lot easier, as you can handle incoming links where it’s most appropriate.

If at all possible, you should use universal links (or Firebase Dynamic Links, which make use of universal links for iOS apps), as these use associated domains to create a connection between a website you own and your app — this will allow you to share data securely.

However, you can still use custom URL schemes to link to content within your app.

Either way, a simple way to trigger a deep link in your app is to use the following command on you development machine:

$ xcrun simctl openurl booted <your url>

An animation of deep linking working in an iPhone app and a corresponding terminal
An animation of deep linking working in an iPhone app and a corresponding terminal
Demo: Opening deep links and continuing user activities

Continuing User Activities

If your app uses NSUserActivity to integrate with Siri, Handoff, or Spotlight, you need to handle user-activity continuation.

Again, the new application–life cycle model makes this easier by providing two modifiers that allow you to advertise an activity and later continue it.

Here’s a snippet that shows how to advertise an activity, for example, in a details view:

To allow continuation of this activity, you can register a onContinueUserActivity closure in your top-level navigation view, like this:

Help — None of the Above Works for Me!

Not all of AppDelegate’s callbacks are supported by the new application life cycle (yet). If none of the above meets your needs, you might require an AppDelegate after all.

Another reason you might require an AppDelegate is if you use any third-party SDKs that make use of method swizzling to inject themselves into the application life cycle. Firebase is a well-known case.

To help you out, Swift provides a way to connect a conformer of AppDelegate with your App implementation: @UIApplicationDelegateAdaptor. Here’s how to use it:

Don’t forget to remove the @main attribute if you copy an existing AppDelegate implementation — otherwise, the compiler will complain about multiple application entry points.

Conclusion

With all this, let’s discuss why Apple made this change. I think there are a couple of reasons:

SE-0281 explicitly states that one of the design goals was “to offer a more general purpose and lightweight mechanism for delegating a program’s entry point to a designated type.”

The DSL-based approach Apple chose for handling the application life cycle aligns nicely with the declarative approach for building UIs in SwiftUI. Using the same concepts makes things easier to understand and will help onboard new developers.

Overall, the new application–life cycle model makes implementing your application startup easier and less convoluted.

The key benefit of any declarative approach is: Instead of putting the burden of implementing a specific functionality onto the developers, the framework/platform provider takes care of this. Should any changes become necessary, it’ll be a lot easier to ship these without breaking many developers’ apps — ideally, developers won’t have to change their implementation, as the framework will take care of everything for you.

Overall, the new application–life cycle model makes implementing your application startup easier and less convoluted. Your code will be cleaner and easier to maintain — and that’s always a good thing, if you ask me.

I hope this article helped you understand the ins and outs of the new application life cycle.

Thanks for reading!

Better Programming

Advice for programmers.

Sign up for The Best of Better Programming

By Better Programming

A weekly newsletter sent every Friday with the best articles we published that week. Code tutorials, advice, career opportunities, and more! Take a look

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Thanks to Zack Shapiro

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store