Where to implement applicationWillResignActive(_:)? - ios

In order for the ViewController to "do something specific" just before the app goes into the background state...
I am to understand that this sort of thing is generally handled inside the
applicationWillResignActive(_:) method, but this method resides inside the AppDelegate class, not the ViewController.
This is my first time doing lifecycle related stuff on IOS, and so I'm not sure whether to:
1) Call a ViewController method from inside the AppDelegate class. This would mean that I have to change the method from private to public.
2) Have the ViewController implement UIApplicationDelegate
PS - Is it okay to just delete the AppDelegate class as long as the ViewController implements UIApplication delegate instead?
EDIT: I should add that this is a single-page app with only one view controller (well, I suppose it will have a settings view controller eventually... but the 'ViewController' that I am referring to will never be popped off the stack).
Thanks!

Generally you shouldn't delete the AppDelegate unless you have a really good reason. This isn't a good reason.
For your scenario I would investigate using NotificationCenter to observe the UIApplicationWillResignActive event. This event is fired every time the application will enter the background.
For more information see: Apple Docs
e.g.
func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated: animated)
NotificationCenter.default.addObserver(self, selector: #selector(youFunction), name: .UIApplicationWillResignActive, object: nil)
}
func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated: animated)
NotificationCenter.default.removeObserver(self)
}

Use NotificationCenter
In YourViewController
class YourViewController : UIViewController {
override func viewDidAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(reloadTableData(_:)), name: .UIApplicationWillResignActive, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: .UIApplicationWillResignActive, object: nil)
}
}
func reloadTableData(_ notification: Notification) {
}

Related

iOS viewWillAppear not being called when returning from background, even with UIModalPresentationStyle.FullScreen, in iOS 13+

Why isn't iOS calling viewWillAppear when our application is returning from the background, even when I've set UIModalPresentationStyle.FullScreen?
viewWillAppear is a function that responds to a view controller's state change. Background and foreground states are different; they are done at an app level.
You can still respond app state changes by using notifications:
override func viewDidAppear(_ animated: Bool) {
// ...
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveForegroundNotification), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func didReceiveForegroundNotification() {
// app returned from the background
}
Your view controller will listen to events until it is deallocated or removed as an observer. If you don't want to execute code when the view controller has dissappeared, you can do it on viewDidDisappear:
override func viewDidDisappear(_ animated: Bool) {
// ..
NotificationCenter.default.removeObserver(self)
}

What are proper places to detect foreground/background delegates in iOS?

Backgound:
I am working in an iOS application. We have around 100 ViewControllers and all of them in our application are inherited from BaseViewController from the beginning. Currently while refactoring, I see many view controllers require to detect willEnterForegroundNotification[1] and didEnterBackgroundNotification[2]
delegates to do some internal tasks. Almost 20~25 view controllers are setting their own notification observers to the delegates on their viewDidLoad. I was thinking to move this detection task to central BaseViewController for code clarity.
My Proposed Solution:
My intended design is like below,
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToForeground), name: Notification.Name.UIApplicationWillEnterForeground, object: nil)
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: Notification.Name.UIApplicationDidEnterBackground, object: nil)
}
func appMovedToBackground() {
print("App moved to Background!")
}
func appMovedToForeground() {
print("App moved to ForeGround!")
}
}
class MyViewController: BaseViewController {
override func appMovedToBackground() {
print(“Do whatever need to do for current view controllers on appMovedToBackground”)
}
override func appMovedToForeground() {
print(“Do whatever need to do for current view controllers on appMovedToForeground”)
}
}
I see that if I move this detection into BaseViewController many tasks of custom observer handling are reduced from child view controllers. Child ViewControllers (i.e. MyViewController in example code) only need to use these two functions appMovedToBackground and appMovedToForeground when they require.
Issues:
However, I am still concern about one thing. As I am moving the observer setting part into BaseViewController, thus all the ViewControllers (approx 100 of them in my project) will register the observer in their default viewDidLoad and many of them won’t even use them in reality. I am afraid this design might heavily costs app performance. Is my intended design acceptable when trading of between performance vs code clarity and maintainability in such situation? Is there any better design in my case?
Reference:
[1] willEnterForegroundNotification - Posted when the app enters the background.
[2] didEnterBackgroundNotification - Posted shortly before an app leaves the background state on its way to becoming the active app.
You can declare a protocol lets call it BGFGObserver.
Let each VC which needs to observe for foreground, background confirm to this protocol.
In base class check if self confirms to BGFGObserver, if yes then only register as observer.
In BGFGObserver you will need to have the methods to handle background and foreground.
Notification is one to many communication. If you really don't this functionality. you can use the protocol delegate method. you can assign a delegate, only when you need it.
and to solve your problem, you can move your observer to didSet of delegate variable. So, only when you assign a delegate, that time only observers will be added. if you don't set it, it will not be added for that viewController.
#objc protocol AppActivityTracker{
func appMovedToBackground()
func appMovedToForeground()
}
class BaseViewController: UIViewController {
var activityDelegate : AppActivityTracker? {
didSet{
//MARK:-Observer will be added only when you assign delegate.
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(activityDelegate?.appMovedToForeground), name: UIApplication.didBecomeActiveNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(activityDelegate?.appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
class MyViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
//MARK:- Assign delegate only when you need observers
activityDelegate = self
}
}
//MARK:- Assign delegate only when you need observers
extension MyViewController : AppActivityTracker{
func appMovedToBackground() {
print("Do whatever need to do for current view controllers on appMovedToBackground")
}
func appMovedToForeground() {
print("Do whatever need to do for current view controllers on appMovedToForeground")
}
}
Discussion from comment:-
Adding observer on viewWillAppear and removing observer on viewDidDisappear.
#objc protocol AppActivityTracker{
func appMovedToBackground()
func appMovedToForeground()
}
class BaseViewController: UIViewController {
var activityDelegate : AppActivityTracker? {
didSet{
if activityDelegate != nil{
addOberservers()
}
else{
removeOberservers()
}
}
}
func addOberservers(){
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(activityDelegate?.appMovedToForeground), name: UIApplication.didBecomeActiveNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(activityDelegate?.appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
func removeOberservers(){
let notificationCenter = NotificationCenter.default
notificationCenter.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
notificationCenter.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil)
}
}
class MyViewController: BaseViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//MARK:- Assign delegate only when you need observers
self.activityDelegate = self
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
//MARK:- Removing observer on view will disappear.
self.activityDelegate = nil
}
}
//MARK:- Assign delegate only when you need observers
extension MyViewController : AppActivityTracker{
func appMovedToBackground() {
print("Do whatever need to do for current view controllers on appMovedToBackground")
}
func appMovedToForeground() {
print("Do whatever need to do for current view controllers on appMovedToForeground")
}
}
You can do this in AppDelegate or create a separate class that is held in AppDelegate to specifically do this.

Get a notification with viewWillAppear - in another VC?

Say you have
var someVC: UIViewController
is it possible to essentially do the following, somehow?
get a notification when {
someVC has a viewWillAppear
self.#selector(wow)
}
#objc func wow() {
print("we spied on that view controller, and it just willAppeared"
}
Is that possible ?
(Or maybe on didLayoutSubviews ?)
(I realize, obviously, you can do this by adding a line of code to the UIViewController in question. That's obvious. I'm asking if we can "add on" to it from elsewhere.)
If I understand your question correctly, you want ViewController B to receive a notification when viewWillAppear is called in ViewController A? You could do this through the Notifications framework. Keep in mind that both VC's have to be loaded for one to receive a notification.
Alternatively, if the two VC's are on the screen at the same time, then I'd recommend a delegate pattern - have VC A tell an overarcing controller class that it's viewWillAppear has been called, and this overarcing controller will then inform ViewController B.
To do this using Notifications:
(This is from memory, so please excuse typos)
class TestClassA: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// To improve this code, you'd pull out the Notification name and perhaps put it into an extension, instead of hardcoding it here and elsewhere.
NotificationCenter.default.post(Notification.init(name: Notification.Name.init(rawValue: "viewControllerAppeared")))
}
}
class TestClassB: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(viewControllerAppeared(notification:)), name: Notification.Name.init(rawValue: "viewControllerAppeared"), object: nil)
}
#objc func viewControllerAppeared(notification: NSNotification) {
print("other viewcontroller appeared")
}
}
Documentation

Swift - notification observer is called several times

I have viewController and inside in viewDidLoad I have
NSNotificationCenter.defaultCenter().addObserver(self, selector: "showNextQuestions", name: "showNextQuestionsID", object: nil)
In another controller I have
NSNotificationCenter.defaultCenter().postNotificationName("showNextQuestionsID", object: nil)
If I go home from app and launch it again function showNextQuestionID fires two times.
I tried to use
func applicationDidEnterBackground(application: UIApplication) {
NSNotificationCenter.defaultCenter().removeObserver(self, name: "showNextQuestionsID", object: nil)
}
But this doesn't help,
and in viewController
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
How can I fix this ?
You are not removing your notification observer in the right place. You register the observer in your view controller subclass, and you need to remove it in the same class. A logical place is to override the viewWillDisappear method. Place the following code in your view controller subclass:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Also remove
NSNotificationCenter.defaultCenter().removeObserver(self, name: "showNextQuestionsID", object: nil)
From your AppDelegate. When you supply the 'self' argument in the AppDelegate, it is referring to the AppDelegate class, not your view controller. When you call to remove your notification observer in the view controller sublcass, self is your view controller, which is what you want.
Last, when you call simply removeObserver(self) with no other arguments, it will unregister all the observers for that object. That way you don't have to go through and list each observer by name.
Put your observers in AppDelegate or a singleton, so that you can easily add and remove an observer during application states.
applicationDidEnterBackground and deinit should be ok.
The problem is the way you are trying to remove the observer in applicationDidEnterBackground. You are trying to remove the observer from AppDelegate and you need to remove the observer from your ViewController.
To fix the problem:
1) Listen for UIApplicationDidEnterBackgroundNotification in your view controller:
func init() {
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "myAppDidEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
}
2) Implement the method that listen to UIApplicationDidEnterBackgroundNotification
func myAppDidEnterBackground() {
NSNotificationCenter.defaultCenter().removeObserver(self, name: "showNextQuestionsID", object: nil)
}
3) Extra. You could also listen for UIApplicationWillEnterForegroundNotification in order to add again your custom notification

IOS Swift handle global events

How can I handle global events triggered by the notification centre for example in my API class I fire an event if an error response is received e.g. (500). When that event is fired an UIAlert should be displayed on what ever view controller is active, or on logout the login view controller should be presented.
As far as I can see there is no easy way to get the current view controller in order to interact with it. (Note that my root view controller is NOT a navigation controller).
An alternative solution, that will work regardless of whether your view controllers are embedded in a UINavigationController or not, would be to subclass UIViewController. This class will handle receiving the NSNotification that an error occurred and will also handle displaying the alert:
class MyViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "errorOccured",
name: "ErrorNotification",
object: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self, name: "ErrorNotification", object: nil)
}
func errorOccured() {
// Present an UIAlertViewController or the login screen.
}
}
Now, any UIViewControllers that should display an alert when the error notification is posted just have to be a subclass of MyViewController. Just make sure, if you override viewWillAppear or viewWillDisappear, that you call super.viewWillAppear or super.viewWillDisappear.
Is this way too hard to get current view controller ( when not using navigation controller ) ?
// on your app delegate
getCurrentViewController(self.window!.rootViewController!)
func getCurrentViewController(viewController:UIViewController)-> UIViewController{
if let navigationController = viewController as? UINavigationController{
return getCurrentViewController(navigationController.visibleViewController)
}
if let viewController = viewController?.presentedViewController {
return getCurrentViewController(viewController)
}else{
return viewController
}
}
For BroadCast Notification
NSNotificationCenter.defaultCenter().postNotificationName("erro400", object: nil)
For Receive
override func viewWillAppear(animated: Bool) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "ErroOccure", name: "erro400", object: nil)
}
func ErroOccure()
{
//present alert from here
// do whatever you want
}
You have to Remove Notification when you finish with it.
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}

Resources