How to prevent SpriteKit game from crashing when Notification Center is opened - ios

I am having a weird issue with a SpriteKit game. The app crashes when a user opens Notification center or Control Center. I want the worldNode layer to be paused and the menuNode layer to be presented when the applicationWillResignActive is called, which is working fine when the home button is pressed. I've tried pausing my game from within the AppDelegate functions applicationWillResignActive and I've tried pausing when applicationDidBecomeActive is called if the game has started. I've tried using the NotificationCenter approach from within the ViewController both work when the Home Button/Home Gesture is used. How Should I handle this? Or is this just a bug in iOS 11?
I have this in the viewDidLoad method of my ViewController
NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
Then each one of those selectors
It crashes on both if trying to present the menu when the appDidBecomeActive and appWillResignActive
#objc func appDidBecomeActive() {
print("appDidBecomeActive")
if GameScene.sharedInstance != nil {
print("presentingMenu")
GameScene.sharedInstance.presentMenuNode()
}
}
#objc func appWillResignActive() {
print("appWillResignActive")
if GameScene.sharedInstance != nil {
print("presentingMenu")
GameScene.sharedInstance.presentMenuNode()
}
}
I feel like I may be trying to approach this the wrong way, but what I don't understand is why does it work when the Home button/Home gesture is fired?
Edit:
After more testing I found that everything works as expected when running on iOS 10 devices. However when I run the DemoBots app that apple provides from their sample code it doesn't crash on iOS 11 and basically does what I want to do, so there has got to be a better way to handle the transitions of the game state, any input is appreciated.

Related

Handle IOS app open from tapping on Now Playing

I'm using SwiftAudio to play audio, I want to detect when audio playing in background mode and app become active from tapping on Now Playing.
NotificationCenter
.default
.addObserver(self, selector: #selector(applicationDidBecomeActive),
name: UIApplication.didBecomeActiveNotification,
object: nil)
#objc func applicationDidBecomeActive() {
print("applicationDidBecomeActive")
}
right now I'm trying this way but it trigger applicationDidBecomeActive even when user open app from icon.
If there any other ways to know when user tapping on Now Playing, please let me know.
I'm very new to IOS development and Swift
Edit: I guess I can detect how app is entered foreground from NowPlaying by passing the expected objectsender to addObserver instead of passing nil object. I tried many times but still don't know what sender object it it.
You should look into MPNowPlayingInfoCenter
When your app is active again, you can check the nowPlayingInfo and playbackState to customise what you actually want to do in your app.
What is the difference between launching from Notification or Now Playing in your case? Maybe clarifying this a bit more will allow us to craft a better solution to your problem.

Detect when app is closed, terminated but ignore when the phone is turned off

I want to know when someone closes my app (taps the home button) or terminates my app (double tap swipe up) however I do not want to know when the user is using my app and simply turns their phone off because when they turn their phone back on it will still be on my app.
I have tried using the applicationWillResignActive, applicationDidEnterBackground, applicationWillTerminate and registering it in my view controller
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.willResignActiveNotification, object: nil)
These either didn't tell me when my app wasn't the focus or if they did work they also told me when my app was still open just the phone was closed.
You cannot draw the distinction you are asking to draw. Whether the user clicks the Home button or turns off the screen, your app is deactivated and backgrounded and you are told so — and that is all you are told.
However, you do not need to draw this distinction. When your app is backgrounded, just do whatever is appropriate. You’ll be told when your app is foregrounded again, even if that is just because the user turned the screen back on.
So after some research the only possible way I found was:
func DidUserPressLockButton() -> Bool
{
let oldBrightness = UIScreen.main.brightness
UIScreen.main.brightness = oldBrightness + (oldBrightness <= 0.01 ? (0.01) : (-0.01))
return oldBrightness != UIScreen.main.brightness
}
func applicationDidEnterBackground(_ application: UIApplication)
{
if (DidUserPressLockButton())
{
//User pressed lock button
}
else
{
//user pressed home button
}}
This method works by seeing if the app can change the brightness however some say this will get rejected by apple because they don't want you to do which makes sense why there isn't a built in function for it however there are apps on the App Store that know how you left the app so there you go

Am I going crazy? UIApplicationWillEnterForegroundNotification not working on simulator or device

It's quite a simple issue. In my view controller I'm registering for a 'foreground' notification:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MyViewController.applicationDidEnterForeground(_:)),
name: UIApplicationWillEnterForegroundNotification, object: nil)
}
func applicationDidEnterForeground(notification: NSNotification) {
print("I'm not going crazy")
}
What I'd Expect
After foregrounding the app, the print statement should be executed and printed to the console.
But...
This never happens. I'm an experienced iOS developer, and I've been trying for an hour or so.
What on earth could I have missed? Is this working for other people? Have tried:
Different values for object, (self, nil,
UIApplication.sharedApplication())
Registering for UIApplicationDidEnterBackgroundNotification - which works fine
Restarting Xcode, Simulator
Running on a device and on the simulator
Cleaning the build folder
It looks like an extension on UIViewController was calling
NSNotificationCenter.defaultCenter().removeObserver(self)
at will, with no regard to any subclass. Not so helpful, and pretty hard to find since extensions could be somewhere completely unrelated to whatever it is you're working on, and not necessarily even part of the view controller's class heirachy.
If you're writing an extension... keep this is mind!!!

Keeping timers running when app enters background

I am making an app that relies on a timer that fires every minute to change a label from "x minutes left" to "(x-1) minutes left. Basically, it decrements the number every minute. I need the timer to function even when I close the app (not entirely, just press the home screen and leave it running in the background), so that when the user comes back to the app after leaving it in the background for 5 minutes, the label will say "(x-5) minutes left".
Right now, when I run it in the simulator it works perfectly, but when I run it on my phone it does not work. If I have the app open on my phone, it works, but if the app is running in the background, the label never decrements. Could this be due to differences in the way the simulator and actual iPhone handle multitasking? If so, how can I change my code so that the timer will still update the label every minute so that the correct number is displayed when the user reopens the app?
Here is my timer setup:
var individualTaskTimer = NSTimer()
func createTimerForTopTask(){
individualTaskTimer = NSTimer.scheduledTimerWithTimeInterval(tasks[0].minutes * 60, target: self, selector: "deleteTopTask", userInfo: nil, repeats: true)
}
As mentioned in Ewan Mellor's answer, you will not be able to rely on a timer while the app is in the background. So you will need to adjust as necessary when your app returns to the foreground.
Upon first reading the documentation, it might seem like viewWillAppear and viewWillDisappear (or viewDidDisappear) are the correct places to handle this. However, they do not get called when the app moves to/from the background.
Instead, you can make use of two notifications, UIApplicationWillResignActiveNotification and UIApplicationDidBecomeActiveNotification. The first notification will be sent to your app when it is about to go into the background. The second notification will be sent to your app when it is about to return to the foreground.
So in viewWillAppear you can register for the notifications as follows:
override func viewWillAppear(animated: Bool) {
// some other code
NSNotificationCenter.defaultCenter().addObserver(self, selector: "activeAgain", name: "UIApplicationDidBecomeActiveNotification", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "goingAway", name: "UIApplicationWillResignActiveNotification", object: nil)
}
where activeAgain and goingAway are two functions you've written to enable and disable the timer. So based on the code snippet in your question, they would look something like this:
func activeAgain() {
let newTime = // calculate how much time is left (in seconds)
individualTaskTimer = NSTimer.scheduledTimerWithTimeInterval(newTime, target: self, selector: "deleteTopTask", userInfo: nil, repeats: true)
}
func goingAway() {
individualTaskTimer.invalidate()
}
Note that you need to unregister for the notifications when you switch away from this view. Doing this in viewWillDisappear is probably a good spot.
You can't do this. iOS will suspend your app after a short while when it goes into the background. Just update your label with the correct time when the app comes back to the foreground.

Prevent SKNode from unpausing automatically when app resumes

In my game, I have functions to pause and unpause an SKNode that contains the gameplay elements. Currently, the system automatically pauses when the home button is pressed, and unpauses when the app becomes active again.
I would like to do this on my own terms. For instance, when the app becomes active again, it should show the pause menu, and stay paused until the user manually unpauses.
Is there a way to override this system behavior?
You can register to receive the UIApplicationDidEnterBackgroundNotification or UIApplicationWillEnterForegroundNotification notification, and trigger the pause menu in the notification selector.
Somewhere in your code, you'd have to register a notification. There are plenty of notifications to choose from. Here is one for when the App comes back from the background:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "didBecomeActive:", name: UIApplicationDidBecomeActiveNotification, object: nil)
}
func didBecomeActive(test: NSNotification) {
self.unpause()
}
Notice that the selector in the addObserver method is the name of the function that will be called when the application comes back from the background. Also the didBecomeActive method needs an argument of type NSNotification.

Resources