NSNotification addObserver in two ViewControllers Swift - 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.

Related

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 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

After seguing from ViewController 1 to ViewController 2 is there a way to let VC2 know that VC1 has completed loading async data (swift)?

There is this post re: passing data between view controllers:
Passing Data between View Controllers
I don't think what i'm after is on there.
In ViewController 1 I load async data from firebase and i'd like to go to ViewController 2 without waiting for VC 1 to return all the data.
On VC2 there is a button that I want disabled until VC 1 has finished.
I did try setting a property in my singleton to true when the data has finished loading and then in VC2 looping until this property returned true but it appears once I have segued from VC1 then it won't set the property in my singleton to true in the background.
Thx.
Of course there are many solutions, you might try to use the NotificationCenter, doing something like:
// in VC2: let's start listening for VC1 callback
NotificationCenter.default.addObserver(self, selector: #selector(self.callbackFromVC1(_:)), name: Notification.Name("VC1HasFinished"), object: nil)
// in VC2: this is the callback will be executed
func callbackFromVC1(_ notification: NSNotification? = nil) {
let userInfoFromVC1:[AnyHashable : Any]? = notification?.userInfo
// do something with userInfoFromVC1
}
....
// in VC1: when your async stuff is finished, push "userInfoFromVC1" into the notification...
NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "VC1HasFinished"), object: nil, userInfo:userInfoFromVC1)

Get applicationDidFinishLaunching call in a View Controller. Parse not initialized yet

I am trying to load data from Parse in my Initial View Controller. The issue is that Parse is initialized in my AppDelegate's didFinishLaunching so I need to wait until it is called before I attempt to load the data from Parse. What is the best way to get this notification in my view controller? Or would it be better to get the data in my AppDelegate?
All help is appreciated!
You can add an observer for the UIApplicationDidFinishLaunchingNotification inside your view controller viewDidLoad method:
NotificationCenter.default.addObserver(self, selector: #selector(didFinishLaunchingNotification), name: UIApplication.didFinishLaunchingNotification, object: nil)
Add your method to the view controller
#objc func didFinishLaunchingNotification(_ notification: Notification) {
// your code
}

How can I use applicationDidBecomeActive in UIViewController?

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