Notification not triggering when view controller updated on Navigation stack changed - ios

I'm trying to send a notification on changing the navigation stack update. But it's not triggered. Here is my code. I have a requirement to change the root view controller on button action. I'm trying the below code, but it's not working for me.
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func replaceThirdViewControllerAsNavigationRoot() {
let nc = NotificationCenter.default
nc.post(name: Notification.Name("Notify"), object: nil)
self.navigationController?.viewControllers = [ThirdViewController.instance()]
}
}
class ThirdViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let nc = NotificationCenter.default
nc.addObserver(self, selector: #selector(userLoggedIn), name: Notification.Name("Notify"), object: nil)
}
#objc func userLoggedIn() {
print("-----")
}
}
extension UIViewController {
static func instance<T: UIViewController>() -> T {
let name = String(describing: self)
guard let controller = UIStoryboard.main.instantiateViewController(withIdentifier: name) as? T else {
fatalError("ViewController '\(name)' is not of the expected class \(T.self).")
}
return controller
}
}
extension UIStoryboard {
static var main: UIStoryboard {
return UIStoryboard.init(name: "Main", bundle: nil)
}
}
Thanks in advance.

The sequence of event is wrong. When you post a notification, you need to make sure that an observer already exists, otherwise the notification will be discarded.
In other words: make sure that
nc.addObserver(self, selector: #selector(userLoggedIn), name: Notification.Name("Notify"), object: nil)
runs before
nc.post(name: Notification.Name("Notify"), object: nil)

Related

NSNotification not observing or posting data

I am trying to learn how to use NSNotification for a project I am working on, and since I have never used it before, I am first trying to learn how to use it first, however; every time I try to follow a youtube tutorial or a tutorial found online my code doesn't seem to be working. Also, when trying to debug the issue, it is showing the observer part of the code isn't going inside the #obj c function. Below is my code showing how it is being used to post and observe a notification.
extension Notification.Name {
static let notifyId = Notification.Name("NotifyTest")
}
ViewController.swift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
var test: ObserverObj = ObserverObj(observerLblText: "Observing")
#IBAction func notifyObserver(_ sender: Any) {
let vc = storyboard?.instantiateViewController(identifier: "ObserverVC")
vc?.modalPresentationStyle = .fullScreen
guard let vcL = vc else {
return
}
NotificationCenter.default.post(name: .notifyId, object: nil)
self.navigationController?.pushViewController(vcL, animated: true)
}
}
NotificationTestViewController.swift
import UIKit
class NotificationTestViewController: UIViewController {
#IBOutlet weak var observerLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(observingFunc(notification:)), name: .notifyId, object: nil)
// Do any additional setup after loading the view.
}
#objc func observingFunc(notification: Notification) {
observerLbl.text = "notifying"//text.test.observerLblText
}
Can someone help me and let me know where I am going wrong as I've been trying for 2 days.
The notification is sent before the observer is added, that means viewDidLoad in the destination controller is executed after the post line in the source view controller.
Possible solutions are :
Override init(coder in the destination controller and add the observer there.
required init?(coder: NSCoder) {
super.init(coder: coder)
NotificationCenter.default.addObserver(self, selector: #selector(observingFunc), name: .notifyId, object: nil)
}
If init(coder is not called override init()
Add the observer before posting the notification (this solution is only for education purpose)
#IBAction func notifyObserver(_ sender: Any) {
guard let vcL = storyboard?.instantiateViewController(identifier: "ObserverVC") as? NotificationTestViewController else { return }
vcL.modalPresentationStyle = .fullScreen
NotificationCenter.default.addObserver(vcL, selector: #selector(NotificationTestViewController.observingFunc), name: .notifyId, object: nil)
self.navigationController?.pushViewController(vcL, animated: true)
NotificationCenter.default.post(name: .notifyId, object: nil)
}
However in practice you are strongly discouraged from sending a notification to a destination you have the reference to.
The reason is that when you send out a notification, your NotificationTestViewController has not yet called the viewdidload method
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
var test: ObserverObj = ObserverObj(observerLblText: "Observing")
#IBAction func notifyObserver(_ sender: Any) {
let vc = storyboard?.instantiateViewController(identifier: "ObserverVC")
vc?.modalPresentationStyle = .fullScreen
guard let vcL = vc else {
return
}
self.navigationController?.pushViewController(vcL, animated: true)
NotificationCenter.default.post(name: .notifyId, object: nil)
}
}

Refresh Storyboard viewcontroller using swift iOS

Im having button in all viewcontrollers to change language
LanguageViewController.swift
class LanguageViewController: UIViewController {
#IBAction func actionChange(_ sender: Any) {
L102Language.currentAppleLanguage()
L102Language.setAppleLAnguageTo(lang: "en")
// below code to refresh storyboard
self.viewDidLoad()
}
}
L102Language.swift
class func currentAppleLanguage() -> String{
let userdef = UserDefaults.standard
let langArray = userdef.object(forKey: APPLE_LANGUAGE_KEY) as! NSArray
let current = langArray.firstObject as! String
let endIndex = current.startIndex
let currentWithoutLocale = current.substring(to: current.index(endIndex, offsetBy: 2))
return currentWithoutLocale
}
/// set #lang to be the first in Applelanguages list
class func setAppleLAnguageTo(lang: String) {
let userdef = UserDefaults.standard
userdef.set([lang,currentAppleLanguage()], forKey: APPLE_LANGUAGE_KEY)
userdef.synchronize()
}
I inherited LanguageViewController in all my FirstViewCOntroller, SecondController as below
class FirstViewController: LanguageViewController {
}
class SecondController: LanguageViewController {
}
If I call self.viewDidLoad() it fails to change language from view defined in storyboard. How to reload storyboard, so that the language should change in all viewcontroller,if any button from any viewcontroller is clicked? Thanks!
You can use NotificationCenter for reloading the view controllers content, this will also reload the content of view controllers that are not visible.
extension Notification.Name {
static let didChangeLanguage = Notification.Name("didChangeLanguage")
}
override func viewDidLoad() {
//Add a listener
NotificationCenter.default.addObserver(self, selector: #selector(onDidChangeLanguage(_:)), name: .didChangeLanguage, object: nil)
}
#IBAction func actionChange(_ sender: Any) {
L102Language.currentAppleLanguage()
L102Language.setAppleLAnguageTo(lang: "en")
// Notify about the change.
NotificationCenter.default.post(name: .didChangeLanguage, object: self, userInfo: nil)
}
#objc func onDidChangeLanguage(_ notification:Notification) {
// reload content using selected language.
}
Correct me if I'm wrong. but I think you don't need to reload all view controllers. you just need to update them when they get displayed, view controllers are behind the presented one are not visible for the user.
for doing that you can do something like this:
var currentLanguage = ""
override func viewDidLoad() {
currentLanguage = currentAppleLanguage()
loadContentForLanguage(currentLanguage)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// this will be executed every time this sceen gets display
if currentLanguage != currentAppleLanguage() {
currentLanguage = currentAppleLanguage()
loadContentForLanguage(currentLanguage)
}
}
func loadContentForLanguage(_ currentLanguage: String) {
//here it goes whatever you currently have in viewDidLoad
}
My apologies if this does not compile, my swift is really rusty.

Show a button on another controller

I have two controller, one controller is controllerOne.swift, in this I receive notifications and I need when one notification arrive, show a button on controllerTwo.swift.
My code is:
ControllerOne.swift
public func websocket(token: Any){
self.ws.open("ws://"+String(APIHOST)+":"+String(port)+"/ws?token="+String(describing: token))
self.ws.event.message = { message in
let res = self.convertToDictionary(text: message as! String)
if ((res!["notification"]) != nil) {
self.count_total_notifications_ws = self.count_total_notifications_ws! + 1
let presentView = UIApplication.shared.keyWindow?.rootViewController?.presentedViewController as? SWRevealViewController
let tabbarController = presentView?.frontViewController as? UITabBarController
if (tabbarController?.selectedIndex != 0) {
tabbarController?.tabBar.items?[0].badgeValue = self.count_total_notifications_ws?.description
}else{
//Here I need to show a showNotificationsbtn button
}
}
}
}
ControllerTwo.swift
class NewDashboardViewController: UIViewController, UITableViewDataSource, UITabBarControllerDelegate, UITableViewDelegate {
//This is the button that I need show
#IBOutlet weak var showNotificationsbtn: UIButton!
#IBAction func showNotifications(_ sender: Any) {true
self.viewDidAppear(true)
showNotificationsbtn.isHidden = true
}
}
Someone know how to I can do?
Thanks for your help.
In ViewControllerOne
if ((res!["notification"]) != nil) {
self.count_total_notifications_ws = self.count_total_notifications_ws! + 1
let presentView = UIApplication.shared.keyWindow?.rootViewController?.presentedViewController as? SWRevealViewController
let tabbarController = presentView?.frontViewController as? UITabBarController
if (tabbarController?.selectedIndex != 0) {
tabbarController?.tabBar.items?[0].badgeValue = self.count_total_notifications_ws?.description
}else{
//Here I need to show a showNotificationsbtn button
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "remoNotificationArrived"), object: nil, userInfo: nil )
}
}
In ViewControllerTwo
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
DispatchQueue.main.async {
NotificationCenter.default.addObserver(self, selector: #selector(self.showButton), name: NSNotification.Name(rawValue: "remoNotificationArrived"), object: nil)
}
}
func showButton(){
showNotificationsbtn.isHidden = false
}
First hide your button.
Now to unhide that button,you have multiple options.
1. Use delgate/protocol for communicating between viewcontrollers
2. You may add an observer

How to unregister an NSNotification in Swift iOS

I have two controllers
class CtrlA: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(CtrlB.self, selector: #selector(CtrlB.badge(notification:)), name: NSNotification.Name(rawValue: "badge"), object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(CtrlB.self, name: NSNotification.Name(rawValue: "badge"), object: nil)
}
}
class CtrlB: UIViewController {
static func badge (notification: NSNotification) {
// blah blah
}
}
Whats the correct way to unregister the notification listener above?
I'm not certain this is correct:
NotificationCenter.default.removeObserver(CtrlB.self, name: NSNotification.Name(rawValue: "badge"), object: nil)
I don't think I can use self either, since it was registered on CtrlB.self
So the best way to implement the notification in your project is create one class called NotificationManager inside that declare one dictionary in which you can always update the observers
class NotificationManager {
var observers = [String: AnyObject]()
}
Create addObserver method, post notification method and remove
observer method inside the same class.
func postNotification(_ name: String, userInfo: [AnyHashable: Any]? = nil) {
NotificationCenter.default.post(name: name, object: nil, userInfo: userInfo)
}
func addObserver(_ name: String, block: #escaping (Notification) -> Void) {
//if observer is already in place for this name, remove it
removeObserverForName(name)
let observer = NotificationCenter.default.addObserver(forName: name), object: nil, queue: OperationQueue.main, using: block)
self.observers[name] = observer
}
func removeObserver(_ name: name) {
guard let observer = self.observers[name] else { return }
NotificationCenter.default.removeObserver(observer)
self.observers.removeValue(forKey: name)
}
//Removes all observers
func removeAllObservers() {
for observer in self.observers.values {
NotificationCenter.default.removeObserver(observer)
}self.observers = [:]
}
So access the above method in any of your class wherever its required and it will take care of everything. This will also prevent crash in your code. If try to remove the same observer more than one time.
I am not sure why you are registering/unregistering to notifications with a class and not an instance. 'CtrlB.self' - will not give you an instance of the CtrlB class, in fact it will return a class itself.
Instead you should use something like this:
class CtrlA {
let ctrlBInstance = CtrlB()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(ctrlBInstance, selector: #selector(CtrlB.badge(notification:)), name: NSNotification.Name(rawValue: "badge"), object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(ctrlBInstance, name: NSNotification.Name(rawValue: "badge"), object: nil)
}
}
And your ClassB should look like this in this case:
class CtrlB {
func badge (notification: NSNotification) {
// blah blah
}
}
You need to get the instance of the observer,which you haven't declared...
for instance you need to set class variable secondA...
class CtrlA: UIViewController {
var secondController: CtrlB?
override func viewDidLoad()
{
super.viewDidLoad()
if let unwrappedController = storyboard.instantiateViewController(withIdentifier: "someViewController") as? CtrlB
{
secondController = unwrappedController
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let secondController = secondController
{
NotificationCenter.default.addObserver(CtrlB.self, selector: #selector(CtrlB.badge(notification:)), name: NSNotification.Name(rawValue: "badge"), object: nil)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let secondController = secondController
{
NotificationCenter.default.removeObserver(CtrlB.self, name: NSNotification.Name(rawValue: "badge"), object: nil)
}
}
//Also don't forget to remove listening on deinit
deinit
{
if let secondController = secondController
{
NotificationCenter.default.removeObserver(secondController, name: NSNotification.Name(rawValue: "badge"), object: nil)
}
}
}
class CtrlB: UIViewController {
//Here you go with notification...
static func badge (notification: NSNotification) {
// blah blah
}
}

NotificationCenter to pass data in Swift

I am working on a test project in Swift 3. I am trying to pass textField string from one class to another class using NotificationCenter. I am trying to workout the answer from this link: pass NSString variable to other class with NSNotification and how to pass multiple values with a notification in swift
I tried few answers from the above link but nothing worked.
My code:
//First VC
import UIKit
extension Notification.Name {
public static let myNotificationKey = Notification.Name(rawValue: "myNotificationKey")
}
class ViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func sendData(_ sender: AnyObject) {
let userInfo = [ "text" : textView.text ]
NotificationCenter.default.post(name: .myNotificationKey, object: nil, userInfo: userInfo)
}
}
//SecondVC
import Foundation
import UIKit
class viewTwo: UIViewController {
#IBOutlet weak var result: UILabel!
override func viewDidLoad() {
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(self.notificationReceived(_:)), name: .myNotificationKey, object: nil)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self, name: .myNotificationKey, object: nil)
}
func notificationReceived(_ notification: Notification) {
guard let text = notification.userInfo?["text"] as? String else { return }
print ("text: \(text)")
result.text = text
}
}
I am not sure whats wrong with the code. Above, code originally marked as a answered which, I found from the first link. Code been converted to Swift.
Don't use object parameter to pass data. It is meant to filter notifications with the same name, but from a particular object. So if you pass some object when you post a notification and another object when you addObserver, you won't receive it. If you pass nil, you basically turn off this filter.
You should use userInfo parameter instead.
First, it is better to define notification's name as extension for Notification.Name. This approach is much safer and more readable:
extension Notification.Name {
public static let myNotificationKey = Notification.Name(rawValue: "myNotificationKey")
}
Post notification:
let userInfo = [ "text" : text ]
NotificationCenter.default.post(name: .myNotificationKey, object: nil, userInfo: userInfo)
Subscribe:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(self.notificationReceived(_:)), name: .myNotificationKey, object: nil)
}
Unsubscribe:
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self, name: .myNotificationKey, object: nil)
}
Method to be called:
func notificationReceived(_ notification: Notification) {
guard let text = notification.userInfo?["text"] as? String else { return }
print ("text: \(text)")
}
Pass text using userInfo which is a optional Dictionary of type [AnyHashable:Any]? in Swift 3.0 and it is [NSObject : AnyObject]? in swift 2.0
#IBAction func sendData(_ sender: UIButton) {
// post a notification
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "notificationName"), object: nil, userInfo: ["text": textValue.text])
print(textValue) // textValue printing
}
in viewDidLoad
// Register to receive notification
NotificationCenter.default.addObserver(self, selector: #selector(self. incomingNotification(_:)), name: NSNotification.Name(rawValue: "notificationName"), object: nil)
and in incomingNotification
func incomingNotification(_ notification: Notification) {
if let text = notification.userInfo?["text"] as? String {
print(text)
// do something with your text
}
}
In your sendData method pass textField.text into Notification object and in your incomingNotification do this:
guard let theString = notification.object as? String else {
print("something went wrong")
return
}
resultLabel.text = theString
You can also use blocks to pass data between controllers.
Use dispatchQueue because your notification is posting before your view load. Therefore just give delay in your notification Post.
#IBAction func sendData(_ sender: AnyObject) {
let userInfo = [ "text" : textView.text ]
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
NotificationCenter.default.post(name: .myNotificationKey, object: nil, userInfo: userInfo) }
}
Just use NotificationCenter to send and receive the notification that state changed. Pass the data through some a data model such as an ObservableObject (particularly if you're bridging between SwiftUI and UIKit). Here's are a couple of extension that make it pretty simple for lightweight inter-component signaling without the cumbersome forgettable semantics of NotificationCenter. (Of course you define your own Notification.Name constants to be meaningful to your purpose).
extension Notification.Name {
static let startEditingTitle = Notification.Name("startEditingTitle")
static let stopEditingTitle = Notification.Name("stopEditingTitle")
}
extension NotificationCenter {
static func wait(_ name : Notification.Name) async {
for await _ in NotificationCenter.default.notifications(named: name) {
break;
}
}
static func post(_ name : Notification.Name) {
NotificationCenter.default.post(name: name, object: nil)
}
#discardableResult static func postProcessing(_ name: Notification.Name, using block: #escaping (Notification) -> Void) -> NSObjectProtocol {
NotificationCenter.default.addObserver(forName: name, object: nil, queue: OperationQueue.main, using: block)
}
}
To post a notification is as simple as:
NotificationCenter.post(.startEditingTitle)
And to receive the notification elsewhere:
NotificationCenter.postProcessing(.startEditingTitle) (_ in {
print("Started editing title")
}
Or to just wait for the notification instead of asynchronously handling it:
NotificationCenter.wait(.startEditingTitle)

Resources