I have an IOS/Iwatch app which uses notifications to broadcast the reception of a message coming from the watch. My problem is that my notifications are sometimes sent (NSNotificationCenter) during the creation of a view that needs to receive it.
Typically it happens after the viewDidLoad() and before the viewDidAppear() hooks. I set the Observer in the first hook :
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "sessionDidEnd", name: MRNotifications.CurrentSessionDidEnd.rawValue, object: session.objectID)
}
But the view seem to never see the notification.
Starting from which state in its lifecycle can a View receive a notification ?
Related
I have an app that does a certain task.
While performing this task, the app also listens for when the user receives a phone call.
If the user receives a phone call, this task needs to be interrupted.
On iOS 13 we listen to willResignActiveNotification for when the incoming call and to didBecomeActiveNotification for when the call ends (this for when the user has the app open before the phone call).
On iOS 14 this also works if the call setting is set to Full Screen.
But when this setting is changed to Banner these notifications are never triggered.
I can't identify the app state for when the setting is set to Banner. My guess is that it is still in the active state.
The problem is that although the notifications are not called, the UI is interrupted as if the app was placed in the background when the user has the Banner setting on.
Note
I also conform to CXCallObserverDelegate and implement callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) method to know when the user receives a call and when the call ends.
So a solution is to just resume the task when the call ends and this method is triggered.
But I want to understand the app lifecycle in this case which is not making sense to me.
Code Sample
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterBackground(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
timer.resume()
updateUI()
}
override func viewWillDisappear(_ animated: Bool) {
timer.invalidate()
pauseUI()
NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
super.viewWillDisappear(animated)
}
#objc private func appDidEnterBackground(_ notification: Notification) {
timer.invalidate()
pauseUI()
}
#objc private func appWillEnterForeground(_ notification: Notification) {
timer.resume()
updateUI()
}
Since iOS 10 our app uses callObserver:callChanged: from CXCallObserverDelegate to detect incoming or answered phone calls like:
#import CallKit;
#property (nonatomic) CXCallObserver *callObserver;
#pragma mark - Application lifecycle
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Register Call Observer to detect incoming calls:
_callObserver = [[CXCallObserver alloc] init];
[_callObserver setDelegate:self
queue:nil];
}
#pragma mark - CXCallObserverDelegate
- (void)callObserver:(CXCallObserver *)callObserver callChanged:(CXCall *)call {
// Based on state:
if (call.hasEnded) {
NSLog(#"CXCallObserver: Call has ended");
}
else if (call.hasConnected) {
NSLog(#"CXCallObserver: Call has connected");
}
else if (!call.isOutgoing && !call.hasConnected && !call.hasEnded) {
NSLog(#"CXCallObserver: Call is incoming");
}
else {
NSLog(#"CXCallObserver: None of the conditions");
}
}
Since the default behaviour on iOS 14 is to show a banner for incoming calls, we indeed saw that CXCallObserverDelegate was not called anymore.
When we changed the Phone setting in the iOS app to Full Screen it did work again. Obviously, just like in iOS 13 and before.
However, when we flipped the setting back to Banner, our app did call CXCallObserverDelegate event on an incoming banner call.
Based on the numerous issues with Local Network permission in iOS 14, it's only an estimated guess that here a similar root cause applies, where settings are only handed over to the native functionality once actively set by the user, and not on a default system setting.
I hope that explicitly changing the setting will solve your issue, or that we were accidentally lucky.
Note: the observations are done on iPhone SE 2nd gen with iOS 14.6.
I am putting ads into my app, but when I click on the banner, it seems like there is a delay when calling my observer method to stop all timers, and are only stopped when the app leaves, not when the ad is clicked, which messes things up a little. so I implemented this method into my App delegate:
func adViewWillLeaveApplication(_ bannerView: GADBannerView) {
print("adViewWillLeaveApplication")
}
Now in the scene where I want to detect when an ad is clicked has this code:
NotificationCenter.default.addObserver(self, selector: #selector(pauseTimers), name: /* what goes here? */, object: nil)
I am unsure of what to put within the comments. I cannot find adViewWillLeaveApplication (and I have imported GoogleMobileAds).
What do I need to put instead?
I am sending local notifications to my users, and I want to show the relevant title on the notification settings button.
If local notifications are off, this title should be "Notifications: off", and if local notifications are on, this title should be something like "Preferences".
Right now I'm checking this in viewDidLoad and viewDidAppear, and it works.
if UIApplication.sharedApplication().currentUserNotificationSettings()?.types.rawValue == 0 {
//the first title
} else {
//the second title
}
Except one case. If my user is changing notifications in the phone settings from "on" to "off" or vice versa, and after that he is back — the title is not changing (because this viewController already loaded and did appear).
How could I check that user is back from the Settings?
You can observe this notification when your app is come to foreground from inactive, the selector will be called everytime your app is opened again from background:
Put in viewDidLoad:
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.reloadData), name: UIApplicationWillEnterForegroundNotification, object: UIApplication.sharedApplication())
Put in viewDidDissapear or deinit:
NSNotificationCenter.defaultCenter().removeObserver(self)
Swift 5.5:
NotificationCenter.default.addObserver(
self,
selector: #selector(yourFunction),
name: UIApplication.willEnterForegroundNotification,
object: UIApplication.shared
)
And:
deinit {
NotificationCenter.default.removeObserver(self)
}
I have a simple game app, which is programmed with SpriteKit. The Problem is, when a push notifications (SMS,iMessage etc) appears, the game stutters because the update:forScene: method is not called.
To avoid this i want to implement a simple pause menu, which will be shown as soon as a push message comes in.
How can i detect if a push message interrupts the app? In AppDelegate application:willResignActive is also not called.
It would be the best if the game continues when the message comes in, if there is another solution to force the update method to be called.
Had anybody the same Problem?
You should not try to resume your game when an interruption is happening, you should pause it, otherwise its not a good user experience.
For iMessages, phone calls etc you usually use the method you said doesn't work.
I use NSNotificationCenter to pause my games, you can google about it, there is plenty tutorials.
Essentially in your game scene add a NSNotificationCenter observer.
Also create a property for that observers name to avoid typos later on.
let pauseGameKey = "PauseGameKey" // above class so you can access it anywhere in project
class GameScene: SKScene {
// add this in didMoveToView
// in #selector add the method you want to get called
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(yourPauseGameMethod), name: pauseGameKey, object: nil)
}
Than create the willMoveFromView method so you can remove the observer when you transition to another scene (good practice).
override func willMoveFromView(view: SKView) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Than in app delegate post the notification when the application will resign.
func applicationWillResignActive(application: UIApplication) {
NSNotificationCenter.defaultCenter().postNotificationName(pauseGameKey, object: nil)
}
For local and remote UINotifications you can additional use these 2 methods in app delegate.
/// Local notification
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
}
/// Remote notification
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
}
Hope this helps
When my watchKit app goes to background it fires the delegate method applicationWillResignActive. Method documentation says it can be used to pause ongoing tasks.
I have an ongoing method that i want to be stopped or broken by the use of the external method. How do i do that?
Example
func method1(){
// performing some actions
}
func breakMethod1(){
// running this method can stop (break) the execution of method1
}
This is, of course, assuming that your app has been architected so that breakMethod1() will definitely cancel the action occurring in method1().
You should set up an observer for an NSNotification at the beginning of method1() like so:
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "breakMethod1", name: UIApplicationWillResignActiveNotification, object: nil)
And for the sake of cleanup, you should also remove this observer after it's been triggered like so:
notificationCenter.removeObserver(self, name: UIApplicationWillResignActiveNotification, object: nil)