Handling applicationDidBecomeActive - "How can a view controller respond to the app becoming Active?"
Asked Answered
H

13

194

I have the UIApplicationDelegate protocol in my main AppDelegate.m class, with the applicationDidBecomeActive method defined.

I want to call a method when the application returns from the background, but the method is in another view controller. How can I check which view controller is currently showing in the applicationDidBecomeActive method and then make a call to a method within that controller?

Hectograph answered 3/9, 2010 at 22:1 Comment(0)
A
315

Any class in your application can become an "observer" for different notifications in the application. When you create (or load) your view controller, you'll want to register it as an observer for the UIApplicationDidBecomeActiveNotification and specify which method that you want to call when that notification gets sent to your application.

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(someMethod:)
                                             name:UIApplicationDidBecomeActiveNotification object:nil];

Don't forget to clean up after yourself! Remember to remove yourself as the observer when your view is going away:

[[NSNotificationCenter defaultCenter] removeObserver:self 
                                                name:UIApplicationDidBecomeActiveNotification
                                              object:nil];

More information about the Notification Center.

Absorbed answered 3/9, 2010 at 22:10 Comment(8)
Excellent. Didn't think of using NSNotificationCenter. Thank you!Hectograph
Just a typo in that line of code (missing 'name'): [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(someMethod:) name:UIApplicationDidBecomeActiveNotification object:nil];Fullrigged
To add to Reed's answer, the method that is called (in this example it's someMethod) needs to accept an NSNotification parameter. So the method signature for someMethod would be -(void)someMethod:(NSNotification *)notification { //Do Something Here }Outrelief
@Outrelief It can, but it's not a requirement. That's great insight, though. Thanks!Absorbed
Fantastic! What a great way to invalidate / recreate NSTimer instances one has going, right in the view controllers / other objects that are responsible for those NSTimers. Love it!Luxembourg
brilliant! bah why didn't i think of that. spent hours trying to figure how to find out which controller was active from app controller.. thanks!Foretooth
@ReedOlsen It isn't recommended to removeObserver:self. In this case you may remove observers, that you're inheriting. The only moment when you can do it, is when you know self will be retained. Better use the pattern to remove each observer separately.Serving
How do I tell when my "view is going away"?Pelham
M
87

Swift 3, 4 Equivalent:

adding observer

NotificationCenter.default.addObserver(self,
    selector: #selector(applicationDidBecomeActive),
    name: .UIApplicationDidBecomeActive, // UIApplication.didBecomeActiveNotification for swift 4.2+
    object: nil)

removing observer

NotificationCenter.default.removeObserver(self,
    name: .UIApplicationDidBecomeActive, // UIApplication.didBecomeActiveNotification for swift 4.2+
    object: nil)

callback

@objc func applicationDidBecomeActive() {
    // handle event
}
Melodist answered 28/9, 2016 at 13:35 Comment(3)
where do I call this?Shurlocke
@user8169082, you add an observer wherever you need to start receiving notifications. You could add it on viewDidLoad or viewWillAppear:animated for instance. And you can remove an observer when you no longer need notifications, or when your observer instance is going to be deallocated in the deinit methodMelodist
swift 4.2 I am using: NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(notification:)), name: UIApplication.didBecomeActiveNotification, object: nil)Interlocutory
C
16

Swift 2 Equivalent:

let notificationCenter = NSNotificationCenter.defaultCenter()

// Add observer:
notificationCenter.addObserver(self,
  selector:Selector("applicationWillResignActiveNotification"),
  name:UIApplicationWillResignActiveNotification,
  object:nil)

// Remove observer:
notificationCenter.removeObserver(self,
  name:UIApplicationWillResignActiveNotification,
  object:nil)

// Remove all observer for all notifications:
notificationCenter.removeObserver(self)

// Callback:
func applicationWillResignActiveNotification() {
  // Handle application will resign notification event.
}
Campus answered 16/8, 2015 at 18:59 Comment(5)
Best place to put removeObserver in Swift: deinit method.Trinitrobenzene
Generally, accessing self in deinit is not advised; at this point, self is in between being fully allocated and being deallocatedCampus
Where would you removeObserver then?Trinitrobenzene
@EnricoSusatyo you can ignore that, as it's not correct. Overriding deinit is fine: "Because an instance is not deallocated until after its deinitializer is called, a deinitializer can access all properties of the instance it is called on and can modify its behavior based on those properties (such as looking up the name of a file that needs to be closed)." Calling deinit is not okUdella
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to remove the observer. The system cleans it. For reference, developer.apple.com/documentation/foundation/notificationcenter/…Tarsus
W
14

Swift 5

fileprivate  func addObservers() {
      NotificationCenter.default.addObserver(self,
                                             selector: #selector(applicationDidBecomeActive),
                                             name: UIApplication.didBecomeActiveNotification,
                                             object: nil)
    }

fileprivate  func removeObservers() {
        NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
    }

@objc fileprivate func applicationDidBecomeActive() {
// here do your work
    }
Whallon answered 8/12, 2019 at 11:31 Comment(0)
D
7

Swift 4.2

Add observer-

NotificationCenter.default.addObserver(self, selector: #selector(handleEvent), name: UIApplication.didBecomeActiveNotification, object: nil)

Remove observer-

NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)

Handle Event-

@objc func handleEvent() {
}
Dwarfish answered 10/11, 2018 at 15:32 Comment(0)
H
6

With Swift 4, Apple advises via a new compiler warning that we avoid the use of #selector in this scenario. The following is a much safer way to accomplish this:

First, create a variable that will hold the observer instance (that will be used to cancel it):

var didBecomeActiveObserver: NSObjectProtocol

Then create a lazy var that can be used by the notification:

lazy var didBecomeActive: (Notification) -> Void = { [weak self] _ in
    // Do stuff
} 

If you require the actual notification be included, just replace the _ with notification.

Next, we set up the notification to observe for the app becoming active.

func setupObserver() {
    didBecomeActiveObserver = NotificationCenter.default.addObserver(
                                  forName: UIApplication.didBecomeActiveNotification,
                                  object: nil,
                                  queue:.main,
                                  using: didBecomeActive)
}

The big change here is that instead of calling a #selector, we now call the var created above. This can eliminate situations where you get invalid selector crashes.

Finally, we remove the observer.

func removeObserver() {
    NotificationCenter.default.removeObserver(didBecomeActiveObserver)
}
Hawkeyed answered 2/3, 2018 at 16:48 Comment(4)
#selector can call a method declared as an @objc attribute in Swift 4.Groundsheet
it is incorrect to use removeObserver(self because self was not assigned when adding observer. You should let observer = NotificationCenter.default.addObserver then removeObserver(observerPushed
Thanks @Hawkeyed I did not know that function yet and it (finally) removes the @objc. However when I try it I get a warning in the console (Xcode 11.3.1 (11C504), Swift 13.3): Can't end BackgroundTask: no background task exists with identifier. Even if I save the observer in a variable as NSObjectProtocol.Resound
Nevermind I also get the warning if I use the @objc variant.Resound
U
5

The Combine way:

import Combine

var cancellables = Set<AnyCancellable>()
NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
    .sink { notification in
            // do stuff
    }.store(in: &cancellables)
Uttica answered 19/3, 2020 at 14:23 Comment(0)
T
5

Swift 5 version:

 NotificationCenter.default.addObserver(self,
                                               selector: #selector(loadData),
                                               name: UIApplication.didBecomeActiveNotification,
                                               object: nil)

Removing the observer is no longer required in iOS 9 and later.

Tarbes answered 31/8, 2020 at 11:48 Comment(0)
D
5

In Swift 5

override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)

         NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive), name: UIApplication.willResignActiveNotification, object: nil)
    
         NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
        }
    
    override func viewWillDisappear(_ animated: Bool) { 
        super.viewWillDisappear(animated)

        NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil)

        NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
    }


@objc private func applicationWillResignActive() {
    }

    @objc private func applicationDidBecomeActive() {
    }
Dwyer answered 1/2, 2021 at 5:56 Comment(0)
C
3

If any of you is using SwiftUI:

.onReceive(NotificationCenter.default.publisher(
    for: UIApplication.didBecomeActiveNotification)) { _ in
        print("DID BECOME ACTIVE")
    }
)
Compelling answered 6/11, 2020 at 2:13 Comment(0)
T
3

Cleaner Swift 5+ solution

Add the observer to init or viewDidLoad:

NotificationCenter.default.addObserver(self, 
                                       selector: #selector(appDidBecomeActive),
                                       name: UIApplication.didBecomeActiveNotification,
                                       object: nil)

You don't need to remove the observer as other answers suggest. It will be done automatically.

@objc private func appDidBecomeActive() {
    // do your magic
}
Teahan answered 18/8, 2022 at 8:22 Comment(0)
R
1

For Swift5 MacOS, you need to use NSApplication instead of UIApplication.

NotificationCenter.default.addObserver(self,
                                       selector: #selector(applicationDidBecomeActive),
                                       name: (NSApplication.didBecomeActiveNotification),
                                       object: nil)
    }
Rowley answered 3/3, 2021 at 5:55 Comment(0)
A
1

In SwiftUI we can use the ScenePhase to observe these changes. e.g

import SwiftUI

struct DetailView: View {
    @Environment(\.scenePhase) private var scenePhase
    var body: some View {
        VStack {
            Text("Hello welcome to swiftui")
        }
        .onChange(of: scenePhase) { newValue in
            if newValue == .active {
                print("DetailView scene updated to active state")
            }
            if newValue == .inactive {
                print("DetailView scene updated to inactive state")
            }
            if newValue == .background {
                print("DetailView scene updated to background state")
            }
        }
    }
}

Now once you put you application to background then following events will happen --

DetailView scene updated to inactive state
DetailView scene updated to background state

Now once you put you application to foreground then following events will happen --

DetailView scene updated to inactive state
DetailView scene updated to active state

You can use access this scenePhase environment object from WindowsGroup, Scene and from any View.

ScenePhase conforms to Comparable and have the following cases -

    /// The scene isn't currently visible in the UI.
    ///
    /// Do as little as possible in a scene that's in the `background` phase.
    /// The `background` phase can precede termination, so do any cleanup work
    /// immediately upon entering this state. For example, close any open files
    /// and network connections. However, a scene can also return to the
    /// ``ScenePhase/active`` phase from the background.
    ///
    /// Expect an app that enters the `background` phase to terminate.
    case background

    /// The scene is in the foreground but should pause its work.
    ///
    /// A scene in this phase doesn't receive events and should pause
    /// timers and free any unnecessary resources. The scene might be completely
    /// hidden in the user interface or otherwise unavailable to the user.
    /// In macOS, scenes only pass through this phase temporarily on their way
    /// to the ``ScenePhase/background`` phase.
    ///
    /// An app or custom scene in this phase contains no scene instances in the
    /// ``ScenePhase/active`` phase.
    case inactive

    /// The scene is in the foreground and interactive.
    ///
    /// An active scene isn't necessarily front-most. For example, a macOS
    /// window might be active even if it doesn't currently have focus.
    /// Nevertheless, all scenes should operate normally in this phase.
    ///
    /// An app or custom scene in this phase contains at least one active scene
    /// instance.
    case active
Anatomist answered 10/1 at 7:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.