UITabBar Lifecycle's methods are not fired from background start - ios

I've have a UITabBar controller as main controller, with 2 tabs. Each tab is a NavigatorViewController with a UIViewController embedded.
If I open the application from background after a previous cold launch, none of the ViewWillAppear (UITabBarController, UIViewController) is fired.
How can I call the lifecycle of UITabBarChildren when user come from backgroud? (IE: From a notification)

That is not in the life cycle because the state of controllers is not changing during background mode or other application events.
You should observe for applicationWillEnterForegroundNotification
class VC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Listen for application event somewhere early like `ViewDidLoad`
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForegroundNotification), name: UIApplication.willEnterForegroundNotification, object: nil)
}
// Implement a function that you want to execute when event happen
#objc func applicationWillEnterForegroundNotification() {
// Do anything before application Enter Foreground
}
// Remove observer when the controller is going to remove to prevent further issues
deinit {
NotificationCenter.default.removeObserver(self)
}
}

When application comes from background non viewWillAppear/viewDidAppear is called for any active vc , you need to listen to app delegate like applicationWillEnterForegroundNotification
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForegroundNotification), name: UIApplication.willEnterForegroundNotification, object: nil)
#objc func applicationWillEnterForegroundNotification(_ notification: NSNotification) {
print("To-Do")
}

You can add an observer to UIApplicationWillEnterForeground in your controllers.
Posted shortly before an app leaves the background state on its way to
becoming the active app.
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,selector: #selector(self.appEnteredFromBackground(_:)),name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
}
#objc func appEnteredFromBackground(_ notification: NSNotification) {
print("From background")
}

Related

View will appear is not firing when we dismiss notification settings overlay

I have a view controller when a button clicked I'm taking the user to app settings.
UIApplication.openApplicationSettings()
When I come back from appsettings to app then viewwillappear method is not firing.
or is there any other method that will let us know the appsettings is dismissed and user is seeing the screen right now.
You should be using app lifecycle events (SceneDelegate/AppDelegate), not view controller lifecycle events (viewDidLoad, viewDidAppear, etc). sceneDidBecomeActive(_:) should be fine for your purposes — for iOS 13+, you should be using SceneDelegate to listen to scene phases, like going to settings (becoming inactive) and then coming back (becoming active again).
/// SceneDelegate.swift
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
/// your code here
}
If you want to listen to sceneDidBecomeActive directly in your view controller, try listening to the didActivateNotification notification.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver( /// add observer
self,
selector: #selector(activated),
name: UIScene.didActivateNotification,
object: nil
)
}
#objc func activated() {
print("View controller is back now")
}
}
Subscribe to following appdelegate events
applicationDidBecomeActive
or
applicationWillEnterForeground
In ViewDidLoad use below code snippet
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
and use below method
#objc func applicationDidBecomeActive(notification: NSNotification) {
updateTableUI()
}
Remove observer
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}

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.

IOS Swift how reload viewcontroller when app come from background?

I want when user call app from background to reaload one function from viewcontroller?
Elaborating on nishith's answer:
Add the following code to your view controller you want to refresh
override func viewWillAppear() {
super.viewWillAppear()
......
......
NotificationCenter.default.addObserver(self, selector:#selector(YourViewController.methodToRefresh), name: UIApplication.willEnterForegroundNotification, object: UIApplication.shared)
......
......
}
Always remember to cleanup when the view disappears in:
override func viewWillDisappear(animated: Bool) {
NotificationCenter.default.removeObserver(self)
}
You can register your controller for these notifications and reload your controller accordingly.
UIApplicationDidEnterBackgroundNotification
UIApplicationWillEnterForegroundNotification

NSNotificationCenter called twice in applicationDidEnterBackground when their is two view controller?

I'm using the the NSNotificationCenter.defaultCenter().postNotificationName function with the applicationDidEnterBackground function. So first I add these to AppDelegate.swift:
func applicationDidEnterBackground(application: UIApplication) {
println("applicationDidEnterBackground")
NSNotificationCenter.defaultCenter().postNotificationName("com.test.mytest", object: self)
}
And I add these to ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "myTestFunc", name: "com.test.mytest", object: nil)
}
func myTestFunc () {
println("CALLED")
}
And up to now, everything is working fine, the console print out the right thing when I enter background:
applicationDidEnterBackground
CALLED
But after I add a new view controller in Storyboard and connect both of them using any of Segue:
And now when I run the app, after I clicked two buttons and then back to the home page, the applicationDidEnterBackground is still calling once but the NSNotificationCenter is called twice:
applicationDidEnterBackground
CALLED
CALLED
So how can I solve this weird problem?
EDIT
I've also tried this, but still same result:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().removeObserver(self, name: "com.test.mytest", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "myTestFunc", name: "com.test.mytest", object: nil)
}
Ah, I forget the viewDidLoad is loaded twice... I solved by:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self, name: "com.test.mytest", object: nil)
}
I have experienced the same problem. In my case solution was enabling Background Modes in project capabilities tab.

Resources