NotificationCenter Selector method is not calling in Swift - ios

I am trying to send first viewcontroller textfield data into second viewcontroller label.
In first controller, Inside send action button adding notification post method
#IBAction func sendtBtn(_ sender: Any) {
let secVc = storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
self.navigationController?.pushViewController(secVc, animated: true)
NotificationCenter.default.post(name: Notification.Name( "notificationName"), object: nil, userInfo: ["text": firstTextField.text])
}
And second viewcontroller addobserver method inside view didload
NotificationCenter.default.addObserver(self, selector: #selector(self.showMsg(_:)), name: Notification.Name( "notificationName"), object: nil)
Selector function :
func showMsg(_ notification: Notification){
print("helloooo")
var vcData = notification.userInfo?["text"]
firstLabel.text = vcData as! String
}
When keeping break points for add observer it observers but it does not calling showMsg function.
Please help me in this code.

You do have the reference to the second view controller. There is no reason at all to use Notification. Don't use notifications if there is only one receiver and the objects are related.
The code doesn't work because the view is not loaded yet when the notification is sent.
Forget the notification. Instead create a property in the second view controller, assign the value in sendtBtn and show the message in viewDidLoad
#IBAction func sendtBtn(_ sender: Any) {
let secVc = storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
secVc.message = firstTextField.text
self.navigationController?.pushViewController(secVc, animated: true)
}
Second view controller
var message = ""
func viewDidLoad() {
super.viewDidLoad()
firstLabel.text = message
}

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

How can I send data from FirstTableView to SecondTableView using Notification Center without segue

I have a problem when I try to pass an array from a UITableView to another one using NotificationCenter Design Pattern (because I don't have a segue between this 2 UIViewControllers). I don't know what I'm doing wrong but I don't receive any data in my second view controller.
My functions looks like this:
* First VC - The Sender Controller (From where I send data) *
class ProductsViewController: UIViewController{
var selectedProductsArray = [Product]()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Implement Notification Design Pattern to send data
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "productsToLoad"), object: selectedProductsArray)
print(selectedProductsArray) // Here I have some data in this array (Photo here: https://ibb.co/k8hoEy)
}
* Second ViewController - The Receiver Controller (Where I will receive the data) *
class CartViewController: UIViewController {
var productsInCartArray = [Product]()
// We retrieve data from "selectedProductsArray" and we append all the products into "productsInCartArray"
#objc func notificationRecevied(notification: Notification) {
productsInCartArray = notification.object as! [Product]
print(productsInCartArray)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Add observer to watch when something was changed in "selectedProductsArray"
NotificationCenter.default.addObserver(self, selector: #selector(notificationRecevied(notification:)), name: NSNotification.Name(rawValue: "productsToLoad"), object: nil)
print(productsInCartArray) // Output: []
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// We remove the observer from the memory
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "productsToLoad"), object: nil)
}
}
Screenshot:
Thank you for your time if you are reading this !
You need to remove this from viewWillDisappear of CartViewController
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "productsToLoad"), object: nil)
as when you post in products the cards are not shown so there is no listener inside it , beside that CartViewController should be opened at least once before you post any data from ProductsViewController
//
you can completely remove the NotificationCenter work , and do this in CartViewController
let products = ((self.tabBarController?.viewControllers![0] as! UINavigationController).topViewController as! ProductsViewController).selectedProductsArray
Note : Don't worry for ! unwrapping it won't crash

close modal callback

I have a view with a table on it, on every table cell I can add some data, so I put it in modal which actually is another view. In my modal I have this code
#IBAction func closeModal(_ sender: Any) {
if let amountVal = amount.text {
if let amountInt = Int16(amountVal) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let activity = Activity(context: context)
activity.created_at = Date() as NSDate
activity.amount = amountInt
countedObject?.addToActivities(activity)
do {
try context.save()
navigationController?.popViewController(animated: true)
} catch let error {
NSLog(error.localizedDescription)
}
}
}
dismiss(animated: true, completion: nil)
}
So I update core data entity and close modal, but after closing modal the first view was not updated so it does not reflect the changes I made until restart of the simulator. In my first view I have this
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(_ animated: Bool) {
populateCountObjects()
}
This works for simple segues but not for modal, what should I use in this case?
So I did it in another way, I added to ViewController this
static var sharedInstace : ViewController?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
ViewController.sharedInstace = self
}
and in the modal after dismiss method
ViewController.sharedInstace?.didCloseModal()
Posible duplicate : duplicate
You need to use a delegate with a dedicated method
protocol ModalDelegate {
func didCloseModal();
}
In your modal controller class, create an instance of ModalDelegate protocol. And call 'delegate.didCloseModal()' before dismiss
Then make your parent controller implement ModalDelegate protocol
and implement the function didCloseModal like viewDidAppear
Let the system do the work for you. In this case you get a notification when the context is saved.
override func viewDidLoad() {
super.viewDidLoad()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let center = NotificationCenter.default
center.addObserver(self, selector: #selector(contextDidSave(_:)), name: .NSManagedObjectContextDidSave, object: context)
}
func contextDidSave(_ notification: Notification) {
populateCountObjects()
}
If that doesn't work for you, you can always go lower and get notified whenever an object is changed.
override func viewDidLoad() {
super.viewDidLoad()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let center = NotificationCenter.default
center.addObserver(self, selector: #selector(contextObjectsDidChange(_:)), name: .NSManagedObjectContextObjectsDidChange, object: context)
}
func contextObjectsDidChange(_ notification: Notification) {
populateCountObjects()
}
I guess your viewDidAppear method isn't called again. Try to use NsNotification to notify your first VC if the context saved and redraw/refresh/reload...etc what you want.

Calling function from another ViewController in swift

I have already looked in Stackoverflow but I can't get an answer. I want to create function that stop playing the sound in another ViewController. But when I clicked the stop button, it cracked and showed "EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)". This is my code.
First ViewController
import UIKit
import AVFoundation
class FirstVC: UIViewController {
var metronome: AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
do {
let resourcePath1 = Bundle.main.path(forResource: "music", ofType: "mp3")
let url = NSURL(fileURLWithPath: resourcePath1!)
try metronome = AVAudioPlayer(contentsOf: url as URL)
metronome.prepareToPlay()
metronome.play()
} catch let err as NSError {
print(err.debugDescription)
}
}
and another Viewcontroller is
import UIKit
class SecondVC: UIViewController {
var metronomePlay = FirstVC()
#IBAction func stopBtnPressed(_ sender: Any) {
metronomePlay.metronome.stop() //"EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)"
}
}
As of swift 4.1 today, this code worked for me:
Put this in sending controller:
NotificationCenter.default.post(name: Notification.Name(rawValue: "disconnectPaxiSockets"), object: nil)
Put this in receiving controller viewDidLoad() or viewWillAppear():
NotificationCenter.default.addObserver(self, selector: #selector(disconnectPaxiSocket(_:)), name: Notification.Name(rawValue: "disconnectPaxiSockets"), object: nil)
and then the following function in your receiving controller class:
#objc func disconnectPaxiSocket(_ notification: Notification) {
ridesTimer.invalidate()
shared.disconnectSockets(socket: self.socket)
}
Swift 5:
Put this in the Action
NotificationCenter.default.post(name: Notification.Name("NewFunctionName"), object: nil)
Put this in viewdidload() in a different viewcontroller (where is the function you want to use)
NotificationCenter.default.addObserver(self, selector: #selector(functionName), name: Notification.Name("NewFunctionName"), object: nil)
The function
#objc func functionName (notification: NSNotification){ //add stuff here}
I hope I was helpful
You are creating a NEW copy of FirstVC and calling stop on something that is not yet initialised.
You should really use a delegate in this case, something like
protocol controlsAudio {
func startAudio()
func stopAudio()
}
class FirstVC: UIViewController, controlsAudio {
func startAudio() {}
func stopAudio() {}
// later in the code when you present SecondVC
func displaySecondVC() {
let vc = SecondVC()
vc.delegate = self
self.present(vc, animated: true)
}
}
class SecondVC: UIViewController {
var delegate: controlsAudio?
// to start audio call self.delegate?.startAudio)
// to stop audio call self.delegate?.stopAudio)
}
So you are passing first VC to the second VC, so when you call these functions you are doing it on the actual FirstVC that is in use, rather than creating a new one.
You could do this without protocols if you like by replacing the var delegate: controlsAudio? with var firstVC: FirstVC? and assigning that, but I wouldn't recommend it
I use this way to call my functions from another viewControllers:
let sendValue = SecondViewController();
sendValue.YourFuncion(data: yourdata);
You can call function from other viewControllers in many ways.
Two ways that are already discussed above are by delegates & protocols and by sending notifications.
Another way is by passing closures to your second viewController from firstVC.
Below is the code in which while segueing to SecondVC we pass a closure to stop the metronome.
There will be no issue because you are passing the same firstVC (not creating a new instance), so the metronome will not be nil.
class FirstVC: UIViewController {
var metronome: AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
do {
let resourcePath1 = Bundle.main.path(forResource: "music", ofType: "mp3")
let url = NSURL(fileURLWithPath: resourcePath1!)
try metronome = AVAudioPlayer(contentsOf: url as URL)
metronome.prepareToPlay()
metronome.play()
} catch let err as NSError {
print(err.debugDescription)
}
let secondVC = SecondVC()
secondVC.stopMetronome = { [weak self] in
self?.metronome.stop()
}
present(secondVC, animated: true)
}
}
class SecondVC: UIViewController {
var metronomePlay = FirstVC()
var stopMetronome: (() -> Void)? // stopMetronome closure
#IBAction func stopBtnPressed(_ sender: Any) {
if let stopMetronome = stopMetronome {
stopMetronome() // calling the closure
}
}
}
var metronomePlay = FirstVC()
you are creating a new instance on FirstVC, instead you should perform the function on the same instance that of already loaded FirstVC.
Updating #Scriptable's answer for Swift 4
Step 1 :
Add this code in your view controller, from which you want to press button click to stop sound.
#IBAction func btnStopSound(_ sender: AnyObject)
{
notificationCenter.post(name: Notification.Name("stopSoundNotification"), object: nil)
}
Step 2:
Now its final step. Now add this below code, to your result view controller, where you want to automatically stop sound.
func functionName (notification: NSNotification) {
metronomePlay.metronome.stop()
}
override func viewWillAppear(animated: Bool) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "functionName",name:"stopSoundNotification", object: nil)
}
You are initialising metronome in viewDidLoad method of FirstVC.
In SecondVC, you are initialising metronomePlay as a stored property, but never asking for ViewController's view and thus viewDidLoad of FirstVC is not getting called which results in metronome(stored property) not getting initialised.
You initialize metronome on FirstVC in viewDidLoad, which won't happen until you load the view of metronomePlay instantiated in SecondVC.
You have to call _ = metronomePlay.view, which will lazily load the view of SecondVC and subsequently execute viewDidLoad, before actually calling metronomePlay.metronome.
Try this in SecondVC. var metronomePlay = FirstVC().metronome
Either use the notification process to stop from anywhere or use same FirstVC instance from SecondVC class.

Open different new view controllers by clicking different elements in table view cell - Swift 3

My table view cell displays an entity with two different button elements. I want to be able to launch a view controller that displays a selection of food items if I click on the first button and a different view controller that displays a selection of beverages when I click on the second button.
I am able to correctly pass the data to the new view controllers, but can't seem to dismiss the current view and load the new one. My code is like this:
In the table view cell
#IBAction func foodBtnPressed(_ sender: Any) {
print("foodBtn pressed")
print("customer is \(customer?.name)")
vc.loadChooserScreen(toChoose: "Food", forCustomer: customer!)
}
#IBAction func beverageBtnPressed(_ sender: UIButton) {
print("beverageBtn pressed")
print("customer is \(customer?.name)")
vc.loadChooserScreen(toChoose: "Beverage", forCustomer: customer!)
}
In the table view controller
func loadChooserScreen(toChoose: String, forCustomer: Customer) {
print("Choose \(toChoose)")
print("For \(forCustomer.name)")
if toChoose == "Food" {
let foodVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "foodMenu") as? FoodVC
foodVC?.loadCustomerToEdit(customer: forCustomer)
dismissVC(sender: Any.self)
}
else if toChoose == "Beverage" {
let beverageVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "beverageMenu") as? BeverageVC
beverageVC?.loadCustomerToEdit(customer: forCustomer)
dismissVC(sender: Any.self)
}
else {
// do nothing
}
}
func dismissVC(sender: Any) {
print("Reached dismissVC function in selectionMenu")
dismiss(animated: true, completion: {
self.delegate!.dismissViewController()
})
}
In this view controller I also have the following protocol
protocol OrderVCProtocol {
func dismissViewController()
}
and have defined
var delegate: OrderVCProtocol!
In my root view controller
func dismissViewController() {
print("Reached dismissViewController function in rootView")
if let foodVC = self.storyboard?.instantiateViewController(withIdentifier: "foodMenu") {
self.present(foodVC, animated: true, completion: nil)
}
if let beverageVC = self.storyboard?.instantiateViewController(withIdentifier: "beverageMenu") {
self.present(beverageVC, animated: true, completion: nil)
}
}
And the delegate is set when the table view controller is called here
#IBAction func loadOrderView(_ sender: Any) {
let orderVC = self.storyboard?.instantiateViewController(withIdentifier: "orderView") as! OrderVC
orderVC.delegate = self
self.present(orderVC, animated: true, completion: nil)
}
Within my target view controllers I have the following function
func loadCustomerToEdit(customer: Customer) {
self.customerToEdit = customer
print("IN FoodVC THE CUSTOMER TO EDIT IS \(self.customerToEdit.name)")
}
and a corresponding one in the BeverageVC.
When I run the app, no errors are thrown and I get the following sample output in the console from my print statements:
foodBtn pressed
customer is Optional("John")
Choose Food
For Optional("John")
IN FoodVC THE CUSTOMER TO EDIT IS Optional("John")
Reached dismissVC function in selectionMenu
and a corresponding response if the beverage button is clicked.
Then nothing happens. So I know the data is correctly being passed to the new view controllers but I don't know how to dismiss the current screen and display the new one with the choices.
I hope my question is clear enough? I'm not sure what's wrong, but the console output clearly shows that the code runs fine until it tries to dismiss the current view.
EDITED TO ADD:
If I modify my dismissVC function in my tableview controller like this:
func dismissVC(sender: Any) {
print("Reached dismissVC function in selectionMenu")
delegate.dismissViewController()
}
the console view now throws
fatal error: unexpectedly found nil while unwrapping an Optional value
And if I modify it again to the following, It goes back to throwing no errors and getting stuck at the same place (i.e. printing the line "Stuck where delegate dismisses view"), showing that the delegate is still nil... but why is it nil when I'd set it in the root view and loaded it in this view?
func dismissVC(sender: Any) {
print("Reached dismissVC function in selectionMenu")
if delegate != nil {
delegate?.dismissViewController()
} else {
print("Stuck where delegate dismisses view")
}
I have solved my problem by implementing notifications via notification centre and delegates. Firstly, in my AppDelegate file I added this line at the bottom
let notifyCnt = NotificationCenter.default
Next, I modified my tableview cell functions to this
#IBAction func foodBtnPressed(_ sender: Any) {
notifyCnt.post(name: NSNotification.Name(rawValue: "toChoose"), object: nil, userInfo: ["toChoose": "Food", "forCustomer": customer])
}
#IBAction func beverageBtnPressed(_ sender: UIButton) {
notifyCnt.post(name: NSNotification.Name(rawValue: "toChoose"), object: nil, userInfo: ["toChoose": "Beverage", "forCustomer": customer])
}
Then, in the tableview controller I modified it to this:
protocol ChooserViewDelegate: class {
func loadChooserView(choice: String, forCustomer: Customer)
}
and defined
weak var delegate: ChooserViewDelegate?
and added this within my ViewDidLoad section
notifyCnt.addObserver(forName: Notification.Name(rawValue: "toChoose"), object: nil, queue: nil, using: loadChooserScreen)
and finally modified my chooser function like so:
func loadChooserScreen(notification: Notification) {
guard let userInfo = notification.userInfo,
let toChoose = userInfo["toChoose"] as? String,
let planToEdit = userInfo["customer"] as? Customer else {
print("No userInfo found in notification")
return
}
delegate?.loadChooserView(choice: toChoose, forCustomer: customer)
}
Then in my root view controller I have the following to replace what I had earlier:
/*Conform to ChooserViewDelegate Protocol */
func loadChooserView(choice: String, forCustomer: Customer) {
self.customer = forCustomer
dismiss(animated: false, completion: {
if choice == "Food" {
self.performSegue(withIdentifier: "food", sender: self.customer)
}
if choice == "Beverage" {
self.performSegue(withIdentifier: "beverage", sender: self.customer)
}
})
}
and I send over the data via prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "food" {
if let foodVC = segue.destination as? FoodVC {
storyboard?.instantiateViewController(withIdentifier: "food")
foodVC.customerToEdit = self.customerToEdit
foodVC.delegate = self
}
}
if segue.identifier == "beverage" {
if let beverageVC = segue.destination as? BeverageVC {
storyboard?.instantiateViewController(withIdentifier: "beverage")
beverageVC.customerToEdit = self.customerToEdit
beverageVC.delegate = self
}
}
}
So now everything loads and views correctly :)

Resources