SwiftUI’s New App Lifecycle and Replacements for AppDelegate and SceneDelegate in iOS 14
During WWDC 2020, SwiftUI got its own app-lifecycle in a bid to get away from UIKit’s AppDelegate and SceneDelegate. To do so, iOS 14 now offers an App
Protocol, a SceneBuilder
, scenePhase
enumerator, and a new property wrapper UIApplicationDelegateAdaptor
.
Before we look at what they are, let’s quickly brush up SceneDelegate
.
SceneDelegate was introduced in iOS 13 primarily to address multi-window support on iPadOS. It brought a transition from the concept of windows to scenes and allowed us to transfer responsibilities from the AppDelegate.
Starting iOS 14, when you create a new SwiftUI Application in an Xcode 12 or above project, you’d be given an option to choose between SwiftUI App Lifecycle and UIKit App Delegate.
While the latter would generate the same old AppDelegate
and SceneDelegate
boilerplate code with UIHostingController
used for embedding SwiftUI Views, the former would greet you with a new starting point, purely for SwiftUI.
SwiftUI App Protocol: A New Starting Point
@main | |
struct ProjectName: App { | |
var body: some Scene { | |
WindowGroup { | |
ContentView() | |
} | |
} | |
} |
While the above piece of code is short, there’s a lot of automatic stuff happening under the hood. Primarily, the struct gives a birds-eye view of a SwiftUI application’s hierarchy by unifying Scenes
andViews
together in one place. A few important things to take note from the code are:
The
@main
attribute was introduced with Swift 5.3 and indicates that the above struct is the starting point of our SwiftUI application.By conforming to the
App
protocol, we’re required to implement aSceneBuilder
which is essentially a function builder for composing one or more scenes. Since we’re using only a single scene in the above example, we’ve implemented the computed propertybody
. Also, theApp
protocol is responsible for triggering themain()
method that launches a SwiftUI application.WindowGroup
in the above example is a container scene that wraps our SwiftUI views. Running the above application on an iPadOS or macOS can create multipleWindowGroup
scenes as they support those features.
Besides WindowGroup
, you can also use a DocumentGroup
scene type for document-based apps or a Settings
and WKNotificationScene
for a macOS or watchOS.
You can also set commands modifier on the WindowGroup
scene to add key shortcuts to the scene. In order to do so, we need to leverage the new CommandsBuilder
to group CommandMenu
that uses Commands
Protocol.
Setting the computed property to @SceneBuilder
is essential when you’re composing complex scenes. For instance, the one given below creates a Preference Menu scene for the macOS platform.
How to Listen to SceneDelegate Lifecycle Methods?
Despite the reduced boilerplate code, one could wonder how to listen to lifecycle updates of a scene — as we do in SceneDelegate
. Gladly, we have a scenePhase
enumerator that holds the current state of the scene. We can use it as an Environment
property wrapper and an onChange
modifier to listen to state changes of our scene as shown below:
@main | |
struct MyApp: App { | |
@Environment(\.scenePhase) private var scenePhase | |
var body: some Scene { | |
WindowGroup { | |
ContentView() | |
} | |
.onChange(of: scenePhase) { phase in | |
if phase == .background { | |
//perform cleanup | |
} | |
} | |
} | |
} |
How to Use AppDelegate With SwiftUI App Protocol
The AppDelegate
class forms a crucial part of our applications. Whether it’s for handling notifications or configuring Firebase, it plays a key role with its different lifecycle methods.
In order to hook the AppDelegate
functionality, we’d need to bring back UIKit (in order to conform to UIApplicationDelegate
) into what was a pure SwiftUI application until now. SwiftUI provides a new property wrapper UIApplicationDelegateAdaptor
through which we can inject the AppDelegate
instance in our SwiftUI structure:
class AppDelegate: NSObject, UIApplicationDelegate { | |
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { | |
return true | |
} | |
} | |
@main | |
struct MyApp: App { | |
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate | |
var body: some Scene { | |
WindowGroup { | |
ContentView() | |
} | |
} | |
} |
Likewise, we can use the WKExtensionDelegateAdaptor
property wrapper to provide the delegate from watchOS.
Conclusion
We saw a new property wrapper for injecting AppDelegate into our SwiftUI structure and a new function builder — SceneBuilder
— for composing scenes.
The new SwiftUI App lifecycle helps kickstart SwiftUI development very quickly. You can leverage the new @SceneStorage
property wrapper for state restoration across multiple scenes of your apps.
Remember SceneDelegates are neither deprecated in iOS 14 nor in the latest SwiftUI iteration. Apple has only brought a new App-Lifecycle for building native SwiftUI apps that don’t depend on UIKit.
Thanks for reading.
Looking to get started with iOS 14? I recommend Programming iOS 14: Dive Deep into Views, View Controllers, and Frameworks, Eleventh Edition (Grayscale Indian Edition)
App Delegate is useless since it only supports the one delegate method. I can't for the life of my figure out how to get the token for pushes since didRegisterForRemoteNotificationsWithDeviceToken NEVER gets called, and my attempt to use UIHostingController to bypass the standard SwiftUI App class results in crashes