avatarPeter Friese

Summary

The provided content discusses the transition from using AppDelegate to the new SwiftUI 2 application life cycle, which introduces a more declarative and DSL-like approach for managing app initialization and state transitions in iOS development.

Abstract

The article "The Ultimate Guide to the SwiftUI 2 Application Life Cycle" delves into the significant changes introduced by Apple in the way iOS applications are initialized and managed with the advent of SwiftUI 2. It explains the shift away from the traditional AppDelegate to a new life cycle that leverages a declarative syntax, aligning with SwiftUI's philosophy. The author outlines how developers can now specify application entry points using the @main attribute and protocol extensions provided by SwiftUI. The article also covers common scenarios for initializing resources and integrating third-party SDKs, demonstrating how to handle these without an AppDelegate. It further explains the use of ScenePhase for tracking app states and the onOpenURL modifier for deep linking, emphasizing the ease of use and maintainability of the new approach. The author acknowledges that while the new life cycle covers many use cases, there are still situations where an AppDelegate might be necessary, and shows how to incorporate it using @UIApplicationDelegateAdaptor. The conclusion summarizes the benefits of the new application life cycle, highlighting cleaner code, easier maintenance, and a smoother onboarding process for new developers.

Opinions

  • The author believes that the new application life cycle model is superior, offering a more elegant and maintainable way of handling app startup and state transitions.
  • The DSL-based approach of the new life cycle is seen as a positive change that aligns well with the declarative nature of SwiftUI, making it easier for developers to understand and implement.
  • The article suggests that the new model reduces complexity and convoluted code, leading to cleaner and more maintainable codebases.
  • The author expresses that the framework taking on more responsibility for application startup is beneficial, as it reduces the burden on developers and allows for easier updates and changes without breaking existing implementations.
  • While the new life cycle is praised, the author recognizes that it may not cover all scenarios, and provides guidance on how to integrate an AppDelegate when necessary, indicating a pragmatic approach to the transition.

The Ultimate Guide to the SwiftUI 2 Application Life Cycle

Goodbye AppDelegate

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:

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>

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!

Further Reading

If you want to learn more, check out these resources:

Programming
Swiftui
Swift
iOS
Mobile
Recommended from ReadMedium