top of page

Swift: Responding to app lifecycle events and using the NotificationCenter

In the previous blog (devrazor.com/post/swift-storing-preferences-in-userdefaults) we need to click on the “Add / Change User Namebutton if we want to add or change the user settings for the default “UserName”. What if we want to be able to add/change the default user name when the user hits the Home button while the application is running? When you hit the Home button and the application is still running, the OS only suspends the application in the background. Since the app is only suspended, it is ready to be fired up again. Click on the app to bring it back. This is very convenient for switching back and forth between applications since it takes less time to wake up an application than to launch it from scratch.


How can we be notified when the application wakes up from its state of suspended as well as when the system suspends the application in the background?


To achieve this we need the controller classes that we have to get notified when the application state changes. As mentioned at the end of my previous blog, a notification represents a simple mechanism that objects can use to communicate with each other.

In the previous example, we may want to call the synchronize() method each time before the application gets suspended in order to save any unsaved user defaults changes and also reload any unmodified user defaults when the application wakes up again.

In the previous blog, we learnt about the app delegate (AppDelegate.swift). It’s the starting point for an iOS app. Its application(_:didFinishLaunchingWithOptions:) function is the first function the operating system calls when starting your app.

Here is where we specified the default values if none are specified in the .plist file.

let mydefaults = ["UserName": "<default_name>"]
UserDefaults.standard.register(defaults: mydefaults)

Prior to iOS13, here is where you would respond to app lifecycle events, such as entering the background, resuming the app, or exiting the app. In iOS 13 (and higher), the scene delegate takes over some of the roles of the app delegate.

In this blog I will use iOS13 and/or higher for the sample code.

The SceneDelegate has now these functions:

  • sceneDidDisconnect() is called when a scene has been disconnected from the app (soon after the scene enters the background)

  • sceneDidBecomeActive() is called when the user starts interacting with a scene, such as selecting it from the app switcher (when the scene moves from an inactive state to an active state)

  • sceneWillResignActive() is called when the user stops interacting with a scene, for example by switching to another scene (when the scene moves from an active state to an inactive state)

  • sceneWillEnterForeground() is called when a scene enters the foreground (when the scene starts or resumes from a background state)

  • sceneDidEnterBackground() is called when a scene enters the background (when the app is minimized but still present in the background)

In this blog we will learn about the UISceneDelegate object to manage life-cycle events, the methods that it has for responding to state transitions that affect the scene, including when the scene enters the foreground and becomes active, and when it enters the background. We will also learn about the NotificationCenter that you can use to broadcast data from one part of your app to another.


The NotificationCenter has three components:

A “listener” that listens for notifications. It’s called an observer

A “sender” that sends notifications when something happens

The notification center itself, that keeps track of the various notifications and observers

In order to register an observer you call the NotificationCenter.default.addObserver

NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIScene.didEnterBackgroundNotification, object: nil)

The above statement, adds an entry with these parameters to the Notification Center, basically telling it that self wants to observe for notifications with name UIScene.didEnterBackgroundNotification, and when that notification occurs, the function didEnterBackground should be called. The fourth parameter object is an optional object whose notifications you want to receive, so if you set it, you’ll only receive notifications from that “sender”.

In this example, the notification UIScene.didEnterBackgroundNotification is posted to the Notification Center when the user leaves the application (e.g. going to the Home screen). Notification Center is written in Objective-C, so the method that a notification triggers has to be visible to the Objective-C runtime so we need to add the @objc annotation to our didEnterBackground function.

@objc func didEnterBackground(notification:NSNotification)

The didEnterBackground function has one parameter, which is a Notification object. The Notification object may have a userInfo dictionary attached to it to send extra data with the notification.

When it comes to intercepting and responding to application/scene lifecycle events you have two options to be notified by these events.

  1. Implement the specific lifecycle method in your app delegate (before iOS 13) or scene delegate (in iOS 13 and higher), or

  2. Register for the UIApplication.<lifecycle event> (before iOS13) or UIScene.<lifecycle event> notification anywhere in your app using the NotificationCenter.default.addObserver.

These notifications (<lifecycle events>) are sent as soon as your app switches from one state to another. For example sceneWillResignActive it's triggered when the user taps the home button once (to return to the home screen) or double taps the home button (to enter multi-tasking).


If you want to go down the application/scene delegate route, you'll find a stub for applicationWillResignActive() already in your AppDelegate.swift file or SceneDelegate.swift file (iOS13 and higher).


In your application you can actually check to see if you have iOS13 or higher in order to use the UIScene otherwise you’ll need to use the UIApplication. You can tell Xcode that you want certain code to execute only on iOS13.0 or later, like this:

if #available(iOS 13.0, *) {
 NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIScene.didEnterBackgroundNotification, object: nil)
 } else {
 NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)

The availability condition #available is going to check whether we're on iOS 13 or later, or any other unknown platforms that might get announced in the future – that's the * at the end, and it's required.

Let’s now create a simple Single View App to see when certain notifications are being fired. Let’s called it test_applifecycle_notificationcenter


I’m using iOS 13.5 for my Target Deployment.

If you open the SceneDelegate.swift file you will notice that the template has already created the Scene state transition delegate methods. Let’s implement these methods so that they print out their names.

Let’s add the following code to each of the Scene Delegate methods:

print("Scene Delegate " + #function)

The literal expression #function inside a function or a method, evaluates to the name of the function or method in which it appears.

 func sceneDidDisconnect(_ scene: UIScene) {
 // Called as the scene is being released by the system.
 // This occurs shortly after the scene enters the background, or when its session is discarded.
 // Release any resources associated with this scene that can be re-created the next time the scene connects.
 // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
 print("Scene Delegate " + #function)
 }

 func sceneDidBecomeActive(_ scene: UIScene) {
 // Called when the scene has moved from an inactive state to an active state.
 // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
 print("Scene Delegate " + #function)
 }

 func sceneWillResignActive(_ scene: UIScene) {
 // Called when the scene will move from an active state to an inactive state.
 // This may occur due to temporary interruptions (ex. an incoming phone call).
 print("Scene Delegate " + #function)
 }

 func sceneWillEnterForeground(_ scene: UIScene) {
 // Called as the scene transitions from the background to the foreground.
 // Use this method to undo the changes made on entering the background.
 print("Scene Delegate " + #function)
 }

 func sceneDidEnterBackground(_ scene: UIScene) {
 // Called as the scene transitions from the foreground to the background.
 // Use this method to save data, release shared resources, and store enough scene-specific state information
 // to restore the scene back to its current state.
 print("Scene Delegate " + #function)
 }

Run the application to see what actions cause what transitions. For example use the Home button to temporary suspend the app and send it to the background. You can tap its icon on the Home screen to relaunch the application again and bring it to the foreground.

Let’s now register the UIScene.<lifecycle event> notification in the ViewController.swift

We will override the viewDidLoad() method to perform the registration.

 if #available(iOS 13.0, *) {
 NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIScene.didEnterBackgroundNotification, object: nil)
 } else {
 NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
 }
 
 if #available(iOS 13.0, *) {
 NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIScene.willEnterForegroundNotification, object: nil)
 } else {
 NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
 }

And here are the corresponding two functions that will be called when the two notifications occur (UIApplication.didEnterBackgroundNotification and UIScene.willEnterForegroundNotification).

 @objc func didEnterBackground(notification:NSNotification) {
 
 print("ViewController " + #function)
 
 }
 
 @objc func willEnterForeground(notification:NSNotification) {
 
 print("ViewController " + #function)
 
 }

Here is what the app will print as a result of 1) launching the app, 2) hitting the Home button and 3) bringing the app into the foreground again.


Scene Delegate sceneWillEnterForeground(_:)

ViewController willEnterForeground(notification:)

Scene Delegate sceneDidBecomeActive(_:)


Scene Delegate sceneWillResignActive(_:)

Scene Delegate sceneDidEnterBackground(_:)

ViewController didEnterBackground(notification:)


Scene Delegate sceneWillEnterForeground(_:)

ViewController willEnterForeground(notification:)

Scene Delegate sceneDidBecomeActive(_:)


You can find the step-by-step instructions for this blog on my devrazor YouTube channel at: ...<stay tuned>


You can also find the complete source code on GitHub: https://github.com/devrazor-com/test_applifecycle_notificationcenter


3,490 views0 comments

Comments


bottom of page