How can I use applicationDidBecomeActive in UIViewController? - iphone

I want to reload data in UIViewController when application become active or become foreground.
I know applicationDidBecomeActive is called in AppDelegate class.
But I have to have a global variable for the UIViewController to reload its data in AppDelegate class like this code:
in AppDelegate.m
// global variable
UIViewController *viewController1;
UIViewController *viewController2;
-(void)applicationDidBecomeActive:(UIApplication *)application
{
[viewController1 reloadData];
[viewController2 reloadData];
}
But it is inconvenient especially when I have a lot of UIViewControllers.
Can I use applicationDidBecomeActive in UIViewController instead of in AppDelegate class?
Or are there better ways than having global variable for UIViewController?
I also need to use the following method from UIViewControllers:
-(void)applicationWillResignActive:(UIApplication *)application
-(void)applicationDidEnterBackground:(UIApplication *)application
-(void)applicationWillEnterForeground:(UIApplication *)application

At the time of reactivation, if you want to carry a particular thing for a view controller, you should register a notification in its viewDidLoad method.
UIApplicationDidBecomeActiveNotification will automatically notify your application and given controller, if they registered for it.
[[NSNotificationCenter defaultCenter]addObserver:self
selector:#selector(yourMethod)
name:UIApplicationDidBecomeActiveNotification
object:nil];

Here is an example of registering a notification handler in Swift (adapted from Apurv's answer above):
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(applicationDidBecomeActive(notification:)),
name: NSNotification.Name.UIApplicationDidBecomeActive,
object: nil)
}
#objc func applicationDidBecomeActive(notification: NSNotification) {
// do something
}
Update for Swift 4.2:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
}
#objc func applicationDidBecomeActive(notification: NSNotification) {
// Application is back in the foreground
print("active")
}

Swift 3:
NotificationCenter.default.addObserver(
self,
selector: #selector(applicationDidBecomeActive(_:)),
name: NSNotification.Name.UIApplicationDidBecomeActive,
object: nil)
func applicationDidBecomeActive(_ notification: NSNotification) {
// do something
}
Note: Don't forget to remove the observer

You cannot use applicationDidBecomeActive in viewController; it is not a method for that class.
However, you can use applicationDidBecomeActive method in AppDelegate to call any methods in your view controller that you feel are important upon launch. Just keep a pointer to your controller so the App Delegate can reach it.
What those methods might be in your view controller is entirely up to you and the details of your program. Maybe it means calling a custom update method in your view controller or whatever else you feel is necessary.
You could also use NSNotificationCenter as outlined here, with many system notifications available for application launch: http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIApplication_Class/Reference/Reference.html
However, relying heavily on NSNotificationCenter is in my opinion a good way for an app to be come disorganized. If you call everything from your main methods in the AppDelegate only, you can always refer to that method to know exactly what your app is doing upon launch. If instead you use NSNotificationCenter, you could have actions spread across many classes/objects and it can be harder to track down what is going on. Since you mentioned multiple controller objects, I think it is more streamlined and organized to call everything from applicationDidBecomeActive rather than register each viewcontroller for the same notification.

update for swift 5
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
}
// remove the observer
deinit {
print("Receiver teardown")
NotificationCenter.default.removeObserver(self)
}
#objc func applicationDidBecomeActive(notification: NSNotification) {
// Application is back in the foreground
print("applicationDidBecomeActive")
}
}

just Use NotificationCenter in your UIViewController :
NotificationCenter.default.addObserver(self,
selector: #selector(applicationWillResignActive),
name: UIApplication.willResignActiveNotification,
object: nil)

Thank you all for answering my question.
But I found easier way to use applicationDidBecomeActive in UIViewController.
#implementation AppDelegate
-(void)applicationDidBecomeActive:(UIApplication *)application
{
UIViewController<MyAppDelegate> *topViewController = (UIViewController<MyAppDelegate> *)navigationController.topViewController;
if ([topViewController respondsToSelector:#selector(MyApplicationDidBecomeActive)]) {
[topViewController MyApplicationDidBecomeActive];
}
}
#end
#protocol MyAppDelegate
#optional
-(void)MyApplicationDidBecomeActive;
#end

Related

Swift calling a ViewController function from the AppDelegate [duplicate]

I am building an iOS app using the new language Swift. Now it is an HTML5 app, that displays HTML content using the UIWebView. The app has local notifications, and what i want to do is trigger a specific javascript method in the UIWebView when the app enters foreground by clicking (touching) the local notification.
I have had a look at this question, but it does not seem to solve my problem. I have also come across this question which tells me about using UIApplicationState, which is good as that would help me know the the app enters foreground from a notification. But when the app resumes and how do i invoke a method in the viewController of the view that gets displayed when the app resumes?
What i would like to do is get an instance of my ViewController and set a property in it to true. Something as follows
class FirstViewController: UIViewController,UIWebViewDelegate {
var execute:Bool = false;
#IBOutlet var tasksView: UIWebView!
}
And in my AppDelegate i have the method
func applicationWillEnterForeground(application: UIApplication!) {
let viewController = self.window!.rootViewController;
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("FirstView") as FirstViewController
setViewController.execute = true;
}
so what i would like to do is when the app enters foreground again, i want to look at the execute variable and run the method as follows,
if execute{
tasksView.stringByEvaluatingJavaScriptFromString("document.getElementById('sample').click()");
}
Where should i put the code for the logic to trigger the javascript from the webview? would it be on viewDidLoad method, or one of the webView delegate methods? i have tried to put that code in the viewDidLoad method but the value of the boolean execute is set to its initial value and not the value set in the delegate when the app enters foreground.
If I want a view controller to be notified when the app is brought back to the foreground, I might just register for the UIApplication.willEnterForegroundNotification notification (bypassing the app delegate method entirely):
class ViewController: UIViewController {
private var observer: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
observer = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [unowned self] notification in
// do whatever you want when the app is brought back to the foreground
}
}
deinit {
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
}
Note, in the completion closure, I include [unowned self] to avoid strong reference cycle that prevents the view controller from being deallocated if you happen to reference self inside the block (which you presumably will need to do if you're going to be updating a class variable or do practically anything interesting).
Also note that I remove the observer even though a casual reading of the removeObserver documentation might lead one to conclude is unnecessary:
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method.
But, when using this block-based rendition, you really do need to remove the notification center observer. As the documentation for addObserver(forName:object:queue:using:) says:
To unregister observations, you pass the object returned by this method to removeObserver(_:). You must invoke removeObserver(_:) or removeObserver(_:name:object:) before any object specified by addObserver(forName:object:queue:using:) is deallocated.
I like to use the Publisher initializer of NotificationCenter. Using that you can subscribe to any NSNotification using Combine.
import UIKit
import Combine
class MyFunkyViewController: UIViewController {
/// The cancel bag containing all the subscriptions.
private var cancelBag: Set<AnyCancellable> = []
override func viewDidLoad() {
super.viewDidLoad()
addSubscribers()
}
/// Adds all the subscribers.
private func addSubscribers() {
NotificationCenter
.Publisher(center: .default,
name: UIApplication.willEnterForegroundNotification)
.sink { [weak self] _ in
self?.doSomething()
}
.store(in: &cancelBag)
}
/// Called when entering foreground.
private func doSomething() {
print("Hello foreground!")
}
}
Add Below Code in ViewController
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector:#selector(appMovedToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func appMovedToForeground() {
print("App moved to foreground!")
}
In Swift 3, it replaces and generates the following.
override func viewDidLoad() {
super.viewDidLoad()
foregroundNotification = NotificationCenter.default.addObserver(forName:
NSNotification.Name.UIApplicationWillEnterForeground, object: nil, queue: OperationQueue.main) {
[unowned self] notification in
// do whatever you want when the app is brought back to the foreground
}

How to determine whether current view controller is active, and execute code if active

In my app, there is a ViewController.swift file and a popupViewController.swift file. Inside the app, when I open the popupViewController with storyboard segue as presentModally and then come back from popupViewController to ViewController with the code dismiss(), the methods viewDidLoad, viewWillAppear, viewDidAppear, ViewWillLayoutSubviews etc. nothing works, they execute just once and don't repeat when I go and return back. So, I want to execute the code every time when viewController.swift is active. I couldn't find a useful info in stackoverflow about this.
Meanwhile, I don't know much about notification and observers(if certainly needed), therefore, can you tell step by step in detail how to do that in Swift (not objective-c)? I mean how to determine if current view controller is active.
Edit: I am navigating from StoryBoard segue, presentModally. There is no Navigation Controller in storyboard.
I tried some codes but nothing happens. The point I came so far is:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector:#selector(appWillEnterForeground), name:UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func appWillEnterForeground() {
print("asdad") //nothing happens
if self.viewIfLoaded?.window != nil {
// viewController is visible
print("CURRENT VİEW CONTROLLER") //nothing happens
}
}
As mention in my comments, I don't use storyboards. There may be a way to create an unwind segue - or maybe not - but [here's a link][1] that may help you with a storyboard-only way of fixing your issue. A quick search on "modal" turned up 9 hits, and the second one starts going into details.
I'm thinking the issue is with what modality is. Basically, your first view controller, which properly executed viewDidAppear, is still visible. So it's effectively not executing viewDidDisappear when your second VC is presented.
You might want to change your concept a bit - an application window (think AppDelegate and/or SceneDelegate become active, where a UIViewController has a is initialized and deinitialized, along with a root UIView that is loaded, appears* and disappears*. This is important, because what you want to do is send your notification from the modal VC's viewDidDisappear override.
First, I find it easiest to put all your notication definitions in an extension:
extension Notification.Name {
static let modalHasDisappeared = Notification.Name("ModalHasDisappeared")
}
This helps not only reduce string typos but also is allows Xcode's code completion to kick in.
Next, in your first view controller, ad an observer to this notification:
init() {
super.init(nibName: nil, bundle: nil)
NotificationCenter.default.addObserver(self, selector: #selector(modalHasDisappeared), name: .modalHasDisappeared, object: nil)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
NotificationCenter.default.addObserver(self, selector: #selector(modalHasDisappeared), name: .modalHasDisappeared, object: nil)
}
#objc func modalHasDisappeared() {
print("modal has disappeared")
}
I've added both forms of init for clarity. Since you are using a storyboard, I'd expect that init(coder:) is the one you need.
Finally, just send the notification when the modal has disappeared:
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.post(name: .modalHasDisappeared, object: nil, userInfo: nil)
}
This sends no data, just the fact that the modal has disappeared. If you want to send data - say, a string or a table cell value, change the object parameter to it:
NotificationCenter.default.post(name: .modalHasDisappeared, object: myLabel, userInfo: nil)
And make the following changes in your first VC:
NotificationCenter.default.addObserver(self, selector: #selector(modalHasDisappeared(_:)), name: .modalHasDisappeared, object: nil)
#objc func modalHasDisappeared(_ notification:Notification) {
let label = notification.object as! UILabel!
print(label.text)
}
Last notes:
To repeat, note that by declaring an extension to Notification.Name, I've only have one place where I'm declaring a string.
There is no code in AppDelegate or SceneDelegate, nor any references to `UIApplication(). Try to think of the view (and view controller) as appearing/disappearing, not background/foreground.
While the first view is visually in the background, it's still visible. So the trick is to code against the modal view disappearing instead.

How do I modify a view from the appdelegate in xcode 10

I have a label that displays the state of the app. as far as I can tell, there is no way to call UIApplicationDelegate protocol methods inside the ViewController and Views can't be linked to AppDelegate. It's like the app is split into two parts that can't communicate with each other.
You can always using notifications...
Apple Docs
Tutorial for listening to application moved to background:
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground),
name: Notification.Name.UIApplicationWillResignActive, object: nil)
}
#objc func appMovedToBackground() {
print("App moved to background!")
}

how to use protocol and delegate methods for viewcontroller that are not directly connected?

Im new to swift and iOS development.I have a doubt regarding protocol and delegate methods.
I have a 4 vc's say vc1,vc2,vc3,vc4.And i'm navigating from vc1->vc2->vc3-vc4->vc1. That is from vc4, im poping using navigation controller back to vc1.
i have a protocol and methods in it like
protocol myProtocol{
func myFunc()
}
In vc4, im making a delegate as,
var delegate:myProtocol?
and im using it in a button action as
if let delegate = self.delegate{
delegate.myFunc()
}
and also pop vc4 back to vc1.
Now in VC1,im extending myProtocol as
class vc1:myProtocol{
override func viewDidLoad()
{
let vcProtocol = vc4()
vc4.delegate = self
}
func myFunc()
{
print("executing this")
}
}
But its not working. Can i do like this?
How can i connect these to classes with delegate and protocol.Please help me
viewDidLoad is only executed once if the controller is not destroyed.
Try calling in viewWillAppear for example.
Also, you do not need to change the delegate of vc4, just set the delegate to vc1 again (in viewWillAppear).
Have you tried Notification?
In your VC1 ViewController ViewDidLoad method:
NotificationCenter.default.addObserver(self, selector: #selector(myFunc, name: NSNotification.Name(rawValue: "aNotificationName"), object: nil)
In your VC4 when the myFunc is needed:
NotificationCenter.default.post(name: Notification.Name("aNotificationName"), object: nil)
You can get the viewController from the navigation stack which has the same type as your first viewController and then assign it as the delegate object in your vc4.
For instance:
In vc4's viewDidLoad, you may do this
for viewController in self.navigationController?.viewControllers{
if viewController.isKindOfClass(YourVC1ControllerType){
self.delegate = viewController
}
}
I assume that your vc1,vc2,vc3, etc have their own ViewController custom classes.
Another way of implementing this would be to get the first controller from the self.navigationController?.viewControllers array and setting it as the delegate object in vc4.
//In viewDidLoad of vc4
delegate = self.navigationController?.viewControllers.first as! HomeController
or,
//In viewDidLoad of vc4
delegate = self.navigationController?.viewControllers.[indexOfTheController] as! HomeController

NSNotification addObserver in two ViewControllers Swift

In my HomeViewController viewDidLoad method I have an observer that looks out for a new notification. When observed it segues to a SecondTableVC. I have an observer in the second VC looking for the same notification, but the second observer isn't seeing the notification and calling the method. Thanks in advance to anyone who can explain what I'm missing here? I removed the observer in both viewDidLoad and in the segue method, but it doesn't fix it.
var childVC: UIViewController!
override func viewDidLoad() {
super.viewDidLoad()
childVC = self.storyboard?.instantiateViewControllerWithIdentifier("WordListsTableViewController")
// check for new notification - if there is segue to the SecondTableVC
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(HomeViewController.showChildVC), name: "NotificationActionPressed", object: nil) // Segue works fine.
}
func showChildVC() {
self.view.addSubview(childVC.view)
}
In SecondTableVC
override func viewDidLoad() {
super.viewDidLoad()
// check for new notification - if there is
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(SecondTableVC.newNotif), name: "NotificationActionPressed", object: nil)
}
func newNotif() {
print("new notif") // THIS METHOD IS NOT GETTING CALLED
}
Kind of piggybacking off of the answer from Phillip: If it is absolutely necessary that the second view controller listen to the NSNotification event, then the second view controller can be instantiated from the storyboard and held in memory by the first view controller until it needs to be displayed. In this case, the second view controller should subscribe to the notification event upon initialization.
Your segue causes the second view controller to be created. If the segue is triggered by a notification, then the SecondTableVC's viewDidLoad hasn't happened when the notification fires.
The second controller's not receiving the notification because it not only hasn't registered by that time, but doesn't actually exist.