swift - How to detect from what ViewController application enter background? - swift

In my swift app I need to know from what screen my application entered background. Im trying to use NotificationCenter this way:
class MainViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackgroundMain), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
#objc func appMovedToBackgroundMain() {
print("main - App moved to Background!")
}
}
class InitViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackgroundInit), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
#objc func appMovedToBackgroundInit() {
print("init - App moved to Background!")
}
}
and when I'm press Home button at the MainViewController I got in Xcode's console these lines:
init - App moved to Background!
main - App moved to Background!
and I expected only one line there - main - App moved to Background!. How can I reach this?

On AppDelegate Methods: applicationDidEnterBackground or applicationWillEnterForeground, you can get the top most UIViewController. It is well explained on this question: Get top most UIViewController

You can use following function:
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
Here you can check which controller is on the top in your navigation controller's controller.
print(self.navigationController.topViewController)

When application enter in background state below method will call.
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}

Related

NotificationCenter - addObserver not called

I am trying a very simple code with NotificationCenter. But the addObserver is not getting called. Can any one of you check and let me know what i am missing. There are 2 simple class, one which post notification and another which listens to it. When i run the program, i just see "sending notification" in the console.
Thanks in advance.
Class 1:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("sending notification")
NotificationCenter.default.post(name: Notification.Name("test"), object: nil)
}
}
Class 2:
class secondvc: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("second vc")
NotificationCenter.default.addObserver(self,
selector: #selector(doThisWhenNotify(_:)),
name: Notification.Name("test"),
object: nil)
}
#objc func doThisWhenNotify(_ notification: Notification) {
print("inside notification")
}
}
If, at the time ViewController comes into existence, secondvc does not yet exist, then there is no one there to receive the posted notification and that is why you don't see the notification being received later when secondvc does come into existence.

What is the best way of updating a variable in a view controller from scene delegate?

I am using Spotify SDK. I want to change labels in some view controllers when a user changes his/her player state. Here is my scene delegate:
var playerViewController = MatchViewController()
func playerStateDidChange(_ playerState: SPTAppRemotePlayerState) {
playerViewController.stateChanged(playerState)
}
A view controller:
func stateChanged(_ playerState: SPTAppRemotePlayerState) {
// aLabel.text = playerState.track.name
}
The problem is labels or other outlets are nil when the state is changed because the view controllers are not loaded at that time. How can I fix that? (I tried isViewLoaded)
If you have a more than a few places to update according to a change that occurs at one place use observers. Here's how,
Post notification in SceneDelegate like this:
func playerStateDidChange(_ playerState: SPTAppRemotePlayerState) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "stateChanged"), object: nil, userInfo: ["playerState": playerState])
}
Observe in ViewControllers like this:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(stateChanged), name: NSNotification.Name("stateChanged"), object: nil)
}
#objc func stateChanged(_ notification: Notification) {
if let playerState = notification.userInfo?["playerState"] as? SPTAppRemotePlayerState {
print(playerState)
}
}
}

prefersStatusBarHidden not updating after calling setNeedsStatusBarAppearanceUpdate()

Different vcs inside my app show the status bar visible and others are hidden. This is set to YES in the info.pList
"View controller-based status bar appearance": YES
// also tried togging this between yes and no
"Status bar is initially hidden": YES
The app has 2 windows, the main window and a second window. The second window gets presented it front of the main window on a button push. The vc in the second window has the status bar hidden.
The problem is if I'm on a vc (mainVC) inside the main window that shows the status bar, I press the button to show the second window, mainVC's status bar disappears. The second window gets presented and after I dismiss it I send a notification to mainVC to call setNeedsStatusBarAppearanceUpdate() but prefersStatusBarHidden isn't triggered so the status bar stays hidden even though it shouldn't be. I even subclassed a Navigation Controller and added the code there with mainVC as it's root.
Why isn't prefersStatusBarHidden getting called?
I added prefersStatusBarHidden inside the mainVC by itself, the nav to the mainVC by itself, and then in both the mainVC and it's nav at the same time. It's still not getting called after setNeedsStatusBarAppearanceUpdate() gets called in either places.
Subclassed nav:
class MainVCNavController: UINavigationController {
override init(rootViewController: UIViewController) {
super.init(rootViewController: rootViewController)
NotificationCenter.default.addObserver(self, selector: #selector(updateStatusBar), name: NSNotification.Name(rawValue: "updateStatusBar"), object: nil)
}
let statusBarHidden: Bool = false
#objc func updateStatusBar() {
self.setNeedsStatusBarAppearanceUpdate() // this gets called when the notification is triggered
}
override var prefersStatusBarHidden: Bool {
return statusBarHidden // this doesn't get called after setNeedsStatusBarAppearanceUpdate() is called
}
// I added this just to see if it would make a difference but it didn't
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .slide
}
override open var childViewControllerForStatusBarStyle: UIViewController? {
return self.topViewController
}
override open var childViewControllerForStatusBarHidden: UIViewController? {
return self.topViewController
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
}
MainVC is the rootVC of the above nav
class MainVCController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(updateStatusBar), name: NSNotification.Name(rawValue: "updateStatusBar"), object: nil)
}
let statusBarHidden: Bool = false
#objc func updateStatusBar() {
self.setNeedsStatusBarAppearanceUpdate() // this gets called when the notification is called
}
override var prefersStatusBarHidden: Bool {
return statusBarHidden // this doesn't get called after setNeedsStatusBarAppearanceUpdate() is triggered
}
// I added this just to see if it would make a difference but it didn't
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .slide
}
}
SecondVC inside the second window has it's status bar hidden. It sends the notification when it's dismissed to the above mainVC:
class SecondController: UIViewController {
override var prefersStatusBarHidden: Bool {
return true
}
if dismissed {
NotificationCenter.default.post(name: Notification.Name(rawValue: "updateStatusBar"), object: nil)
}
}
I also read that I need to call the below to trigger the prefersStatusBarHidden but even when I added these to the updateStatusBar() it didn't make a difference.
navigationController?.setNavigationBarHidden(false, animated: false)
// or
navigationController?.navigationBar.isHidden = false
Updating the status bar needs to be on the main thread.
There are two ways to ensure that:
Add notification observer on the main thread: (you don't need to expose the func to objc c):
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "updateStatusBar"), object: nil, queue: .main, using: updateStatusBar)
func updateStatusBar(_ notification: Notification) {
setNeedsStatusBarAppearanceUpdate()
}
Or update the status bar on the main thread:
NotificationCenter.default.addObserver(self, selector: #selector(updateStatusBar(_:)), name: NSNotification.Name(rawValue: "updateStatusBar"), object: nil)
#objc func updateStatusBar(_ notification: Notification) {
DispatchQueue.main.sync {
self.setNeedsStatusBarAppearanceUpdate()
}
}
#zombie’s answer about the updating on the main thread 100% worked. On another note he also suggested I use symbolic breakpoints to diagnose the problem. He provided a great link to help:
Symbolic Breakpoints

Locations in swift

I want to update the location when the user enters the app again. If a user opens the app you get correct data, but when you close the app (homebutton) and open it again it goes in the refresh function, but does not go in the location function. I can't get it. Here is my code:
import UIKit
import CoreLocation
var request : NSMutableURLRequest = NSMutableURLRequest()
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
func refresh(){
println("update")
locationManager.startUpdatingLocation()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!)
{
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: {(placemarks, error)->Void in
if (error != nil)
{
println("Error: " + error.localizedDescription)
return
}
if (placemarks.count > 0)
{
let pm = placemarks[0] as! CLPlacemark
self.displayLocationInfo(pm)
}
else
{
println("Error with the data.")
}
})
}
in my app delegate i have this:
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
ViewController().refresh();
}
So it goes in the refresh function, but than it does noting.. What can it be?
Thank you!
in each call of applicationWillEnterForeground(application: UIApplication) you create a new object of ViewController, call refresh() on it and let it go away. This is not the view controller object, that is presented to the user.
Instead you must get the presented ViewController object and call refresh() on it.
It could be something like
func applicationWillEnterForeground(application: UIApplication) {
let viewController= self.window.rootViewController as! ViewController
viewController.refresh()
}
But how it looks exactly, depends on details in your code we don't know.
or delete that line altogether and subrcribe for the WillEnterForeground notification in your ViewController's viewDidLoad()
override func viewDidLoad()
{
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationWillEnterForeground:", name: UIApplicationWillEnterForegroundNotification, object: nil)
}
func applicationWillEnterForeground(notification: NSNotification) {
self.refresh()
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
The challenge you have is that when you press the home key, your views do not unload but are merely suspended. Therefore with this, the viewDidLoad method does not get called, when your application resumes...
What you are actually doing is dealing with the application coming into the foreground which you have to register to receive notifications about. Add the following line to your viewDidLoad method:
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("applicationWillEnterForeground"), name: UIApplicationWillEnterForegroundNotification, object: nil)
That will add an observer for the UIApplicationWillEnterForegroundNotification event and then call the method applicationWillEnterForeground. Now all you need to do is create the method applicationWillEnterForeground and call your refresh method from within it.

In a Swift application why WatchKit and NSNotificationCenter is working one way only?

When a NSNotificationCenter notification send from WatchKit Extension InterfaceController we can get it from ViewController but when notification send from ViewController we can not get it from WatchKit Extension InterfaceController.
Here is the working notification;
We set notification in ViewController as
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "action_notification_WatchButton:", name: "notification_WatchButton", object: nil)
}
We get notification from in ViewController with
func action_notification_WatchButton(notification:NSNotification) {
println("watch button touched")
}
We send notification from WatchKit Extension InterfaceController as
#IBAction func buttonWatch() {
NSNotificationCenter.defaultCenter().postNotificationName("notification_WatchButton", object:nil, userInfo:nil)
}
So that works while both watch and phone applications are running. When we touch button watch
println("watch button touched")
prints the text. Very nice.
But when we set notification in
override func willActivate() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "action_notification_WatchButton:", name: "notification_WatchButton", object: nil)
}
and we put
func action_notification_WatchButton(notification:NSNotification) {
println("phone button touched")
}
under InterfaceController at WatchKit Extension and we use
#IBAction func buttonPhone() {
NSNotificationCenter.defaultCenter().postNotificationName("notification_WatchButton", object:nil, userInfo:nil)
}
inside of ViewController button function there is no answer as the button touched on Phone.
You should read up on Darwin notifications and MMWormhole. That will solve your notification issues.