I'm working on SDK and try to catch app termination Notification. It's easy to get this as closure for (ex) NSNotification.Name.UIApplicationWillResignActive
self.resignActiveNotification = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillResignActive,
object: nil,
queue: nil) { _ in
//something goes here and it works like a charm.
}
So I want to have similar behavior for termination:
self.terminateNotification = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillTerminate,
object: nil,
queue: nil) { _ in
NSLog("%#",#function)
}
And this one never gets called!
Of course if I put in AppDelegate:
func applicationWillTerminate(_ application: UIApplication) {
//termination
}
It will work, but since I'm building an SDK I cannot have AppDelegate methods. There is a way to get termination closure call? Or any other way to know that application is about to terminate?
In Apple documentation you can find:
After calling this method, the app also posts a
UIApplicationWillTerminate notification to give interested objects a
chance to respond to the transition.
However this seems to be broken
This seems working for me:
init() {
NotificationCenter.default.addObserver(self,
selector: #selector(applicationWillTerminate(notification:)),
name: UIApplication.willTerminateNotification,
object: nil)
}
#objc func applicationWillTerminate(notification: Notification) {
// Notification received.
}
deinit {
NotificationCenter.default.removeObserver(self)
}
Related
If I needed to listen for a notification, I've always done like so:
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: UIApplication.didEnterBackgroundNotification, object: nil)
But if I move that line within the body of an async method, the compiler suddenly wants that line to be marked with await:
func setup() async {
let importantStuff = await someTask()
// store my important stuff, now setup is complete
// This line won't compile without the await keyword.
// But addObserver is not even an async method 🤨
await NotificationCenter.default.addObserver(self, selector: #selector(foo), name: UIApplication.didEnterBackgroundNotification, object: nil)
// But this works fine
listen()
}
func listen() {
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
Minimal Example
This code does not compile (compiler wants to add await on the addObserver call).
class Test {
func thisFailsToCompile() async {
let stuff = try! await URLSession.shared.data(from: URL(string: "https://google.com")!)
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
#objc func foo() {
}
}
What's going on here?
The problem is that UIApplication is #MainActor and didEnterBackgroundNotification is a property on it, and it is not marked nonisolated, so it can only be accessed on the main actor. This is likely an oversight by Apple, since didEnterBackgroundNotification is a constant, and should be able to be marked nonisolated. But it's possibly also a limitation in the ObjC/Swift bridge.
There are several fixes for this depending on how you want it to work.
The least-impacting change (other than just keeping the await) is to cache the value synchronously:
class Test {
// Cache the value of didEnterBackgroundNotification so that async code doesn't have to access UIApplication
private let didEnterBackgroundNotification = UIApplication.didEnterBackgroundNotification
func thisFailsToCompile() async {
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: didEnterBackgroundNotification, object: nil)
}
#objc func foo() { }
}
Or, you can mark Test as #MainActor. "UI" things should almost always be #MainActor, so it depends on what kind of object this is.
#MainActor
class Test { ... }
Or, you can mark just the relevant method as #MainActor (though this probably isn't a great solution for your problem):
#MainActor func thisFailsToCompile() async {
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
Note that Notifications are delivered synchronously on the queue that calls postNotification. This means that all UIKit (including UIApplication) notifications arrive on the main queue. In fact, most notifications come in on the main queue. This isn't required or enforced (so local notifications in your app may come in on random queues), but it is very common. Because of this, if you listen to Notifications, it is often very useful to make the object #MainActor.
(But hopefully Apple will also fix this silliness about various constants being isolated.)
The compilers wants you to add await because of the notification name. It seems that UIApplication.didEnterBackgroundNotification inside async method requires to await the addObserver method.
It does not occurs with another notifications, but with this type it's required. It might be because of some Apple implementation.
I'm currently developing a framework through which i want to check if the app has been launched and if it is in foreground or not.
The thing is that UIApplication cannot be imported in a framework. Is there a way to catch that event without having to do it in the app ?
Instead of accessing the AppDelegate, which is a bad coding style in my opinion, you may listen for the events instead:
func startListen() {
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForeground(_:)), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
}
func stopListen() {
NotificationCenter.default.removeObserver(self)
}
func applicationWillEnterForeground(_ notification: NSNotification) {
print("App moved to foreground!")
}
There are more notification types like UIApplicationDidEnterBackground that may notify you about the whole app lifecycle.
I have a strange issue. I register and unregister my Notification like so:
func doRegisterNotificationListener() {
NotificationCenter.default.addObserver(forName: Notification.Name(rawValue: "RateAlertRated"), object: nil, queue: nil, using: rateDidRate)
}
func doUnregisterNotificationListener() {
NotificationCenter.default.removeObserver(self, name: Notification.Name(rawValue: "RateAlertRated"), object: nil)
}
func rateDidRate(notification: Notification) {
let rating = notification.userInfo?["score"] as? Int
let message = notification.userInfo?["message"] as? String
let response = Response(rating: rating, message: message)
output.presentRated(response)
}
This view controller is in a UITabBarController. doRegisterNotificationListener is called in viewDidAppear and doUnregisterNotificationListener is called in viewDidDisappear. When I switch between tabs the register and unregister methods are being called correctly (I tested using a print statement). However if I fire a notification it will still be received even though doUnregisterNotificationListener was called last. Any ideas what I might be doing wrong here?
Quick note:
Also tried:
NotificationCenter.default.removeObserver(self)
This also doesn't work.
I have tested your code and once i register observer with this type it is not called when you doUnregister it. please try this.
NotificationCenter.default.addObserver(self, selector: #selector(rateDidRate(notification:)), name: Notification.Name(rawValue: "RateAlertRated"), object: nil)
If you are working with addObserver(forName:object:queue:using:) you should remove it in this way:
Create:
let center = NSNotificationCenter.defaultCenter()
let mainQueue = NSOperationQueue.mainQueue()
self.localeChangeObserver = center.addObserverForName(NSCurrentLocaleDidChangeNotification, object: nil, queue: mainQueue) { (note) in
print("The user's locale changed to: \(NSLocale.currentLocale().localeIdentifier)")
}
Remove:
center.removeObserver(self.localeChangeObserver)
This approach is taken from the documentation.
I'm working on a game for iOS coded in Swift. I've tried to find a way to detect when the app enters background mode or is interrupted for other reasons, for example a phone call but can't find anything. How do I do it?
You can add an observer to your view controller:
edit/update: Xcode 11 • Swift 5
iOS13 or later
UIScene.willDeactivateNotification
iOS12 or earlier
UIApplication.willResignActiveNotification
if #available(iOS 13.0, *) {
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIScene.willDeactivateNotification, object: nil)
} else {
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIApplication.willResignActiveNotification, object: nil)
}
and add a selector method to your view controller that will be executed when your app receives that notification:
#objc func willResignActive(_ notification: Notification) {
// code to execute
}
In swift 5.x: To observe app enters background event, add this code to your viewDidLoad() method.
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
#objc func appMovedToBackground() {
// do whatever event you want
}
you have to use UIApplication.didEnterBackgroundNotification.
If you want to observe if app came to foreground event, use UIApplication.willEnterForegroundNotification
So, the full code will be:
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(appCameToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
// Do any additional setup after loading the view.
}
#objc func appMovedToBackground() {
print("app enters background")
}
#objc func appCameToForeground() {
print("app enters foreground")
}
Swift3
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: Notification.Name.UIApplicationWillResignActive, object: nil)
func appMovedToBackground() {
print("App moved to background!")
}
To detect the app enters background, you can check in the appDelegate.m
find the application delegate method
applicationDidEnterBackground
This method will get called, once the app enters background.
SwiftUI
From background
Text("Hello, World!")
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
print("To the foreground!")
}
To the background
Text("Hello, World!")
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in
print("To the background!")
}
For SwiftUI you can use:
YourView()
.onReceive(NotificationCenter.default.publisher(for: UIScene.willDeactivateNotification)) { _ in
//...
}
Take a look at the delegate methods defined in your instance of UIApplicationDeletegate (called AppDelegate.m by default). Specifically the following would be useful:
- (void)applicationWillResignActive:(UIApplication *)application
This method is called to let your app know that it is about to move from the active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the app and it begins the transition to the background state. An app in the inactive state continues to run but does not dispatch incoming events to responders.
Taken from the Apple Documentation - here
After converting my Objective-C code to Swift, I cannot get my NSNotifications to work. After an hour of searching in the web, I finally gave up. Consider the following example:
func getToUrl(url:String, timeoutInterval:Float) -> Bool {
println("Starting HTTP GET to: \(url)")
// Fire a notification
NSNotificationCenter.defaultCenter().postNotificationName("StartNotification", object: self)
[...]
}
func getJsonFromServer() {
// Add an observer which should fire the method test when desired
NSNotificationCenter.defaultCenter().addObserver(self, selector: "test:", name: "StartNotification", object: self)
// Calls the function
getToUrl("http://www.stackoverflow.com", timeoutInterval: 10)
}
func test(sender: AnyObject) {
println("I am here!")
}
I cannot find the error, I would really appreciate if someone else could!
The code runs, but the test method is never called.
Change in this, self to nil (in order to hear all objects)
func getJsonFromServer() {
// Add an observer which should fire the method test when desired
NSNotificationCenter.defaultCenter().addObserver(self, selector: "test:", name: "StartNotification", object: nil)
// Calls the function
getToUrl("http://www.stackoverflow.com", timeoutInterval: 10)
}