Why Unrecognized selector sent to instance? - ios

I'm trying to make a convenient Binding keyboard to a uiview function. I can't get across this error
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Twitter.LoginVC handleKeyboard:]: unrecognized selector sent to instance 0x7ffbf142e970'
class KeyboardService {
var constraint: NSLayoutConstraint!
var vc: UIViewController!
func bind(bottomConstraint: NSLayoutConstraint, vc: UIViewController) {
constraint = bottomConstraint
self.vc = vc
NotificationService.instance.addKeyboardObservers(onVC: vc, handleKeyboardSelector: #selector(self.handleKeyboard(_:))) // **CRASHES HERE**
}
#objc func handleKeyboard(_ notification: NSNotification) {
NotificationService.instance.handleKeyboard(notification: notification, bottomConstraint: constraint, vc: vc)
}
}
Here's my notificationService:
class NotificationService {
static let instance = NotificationService()
func addKeyboardObservers(onVC vc: UIViewController, handleKeyboardSelector: Selector) {
NotificationCenter.default.addObserver(vc, selector: handleKeyboardSelector, name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(vc, selector: handleKeyboardSelector, name: UIResponder.keyboardWillHideNotification, object: nil)
}
}
EDIT:
class KeyboardService {
var constraint: NSLayoutConstraint!
var vc: UIViewController!
func bind(bottomConstraint: NSLayoutConstraint, vc: UIViewController) {
constraint = bottomConstraint
self.vc = vc
NotificationService.instance.addKeyboardObservers(self, handleKeyboardSelector: #selector(self.handleKeyboard(_:)))
}
#objc func handleKeyboard(_ notification: NSNotification) {
NotificationService.instance.handleKeyboard(notification: notification, bottomConstraint: constraint, vc: vc)
}
}
EDIT 2:
class KeyboardService {
var constraint: NSLayoutConstraint!
var vc: UIViewController!
func bind(bottomConstraint: NSLayoutConstraint, vc: UIViewController) {
constraint = bottomConstraint
self.vc = vc
NotificationService.instance.addKeyboardObservers(self, handleKeyboardSelector: #selector(handleKeyboard(_:)))
}
#objc func handleKeyboard(_ notification: NSNotification) {
NotificationService.instance.handleKeyboard(notification: notification, bottomConstraint: constraint, vc: vc)
}
}
In viewDidLoad() of a vc:
KeyboardService().bind(bottomConstraint: loginBtnBackViewBottomConstraint, vc: self)

You are trying to sent the selector to vc; but it's a UIViewController that doesn't actually have a method called handleKeyboard(_:). You should change to this your registering method:
func addKeyboardObservers(_ observer: Any, handleKeyboardSelector: Selector) {
NotificationCenter.default.addObserver(observer, selector: handleKeyboardSelector, name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(observer, selector: handleKeyboardSelector, name: UIResponder.keyboardWillHideNotification, object: nil)
}
And then when you use it, you'd do:
NotificationService.instance.addKeyboardObservers(self, handleKeyboardSelector: #selector(self.handleKeyboard(_:)))
As you can see you're now telling to trigger the selector on self, because it's actually self that has that method.
As a general rule: a selector is sent to an instance, so it's that instance that must have the chosen method.
If you really want to send the selector to a viewController instance you could create an extension that adds the method to every UIViewController
extension UIViewController {
#objc func handleKeyboard(_ notification: NSNotification) {
// do your stuff here
}
}
Then, on registration do:
NotificationService.instance.addKeyboardObservers(onVC: vc, handleKeyboardSelector: #selector(vc.handleKeyboard(_:))) // notice the vc.handleKeyboard instead of self.handleKeyboard
EDIT
Try to retain the keyboard service in the view controller:
let keyboardService = KeyboardService()
override func viewDidLoad() {
super.viewDidLoad()
keyboardService.bind(bottomConstraint: loginBtnBackViewBottomConstraint, vc: self)
}

Related

why NotificationCenter.observer is not listening to notifications

Hai guys I am new to iOS development and still learning
I have a three View controllers ,viewController, SViewController, TViewController
in SviewController I have notification sender.post method on clicking a button
in viewController, TViewController viewdidload() methods I have .addobserver() methods
when I click a button on SViewController and post the notification
View Controller's selector is executed and not the TviewController
I have loaded the TviewController in FirstVC i.e ViewController viewdidload() method only
since segue performs again a viewdidload() from SviewController to TviewController with another button ,I tried to allocate a completion handler string to global variable so that it value remains same and print it(changed value) when view is loaded again, then I came to know that the completion handler is not at all executing here is the code
ViewController
import UIKit
extension Notification.Name{
static var fnote = Notification.Name("fnote")
}
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(fhandle(notification:)), name: .fnote, object: nil)
let storyboad = UIStoryboard(name: "Main", bundle: nil)
let tvc = storyboad.instantiateViewController(identifier: "1") as! TViewController
let _ = tvc.view
print(tvc.isViewLoaded)
print("journey")
}
#objc func fhandle(notification:Notification){
label.text = "Haii welcome to the hyderabad"
}
}
SViewController
import UIKit
var temp:String = "HHH"
class SViewController: UIViewController {
#IBAction func click(_ sender: Any) {
NotificationCenter.default.post(name: .fnote, object: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
TviewController
import UIKit
class TViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(handler(notification:)) , name: .fnote, object: nil)
print(temp)
print("happy")
}
#objc func handler(notification:Notification)
{
print("jackson")
label2.text = "Hurray"
temp = label2.text ?? "Nothing"
}
}
Can Some one please help me with this
You haven't shown or explained where / when / how you're going to display the view from TViewController, but to explain the first thing you're doing wrong...
In this code:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(fhandle(notification:)), name: .fnote, object: nil)
let storyboad = UIStoryboard(name: "Main", bundle: nil)
let tvc = storyboad.instantiateViewController(identifier: "1") as! TViewController
let _ = tvc.view
print(tvc.isViewLoaded)
print("journey")
}
as soon as viewDidLoad() finishes (that is, immediately after the print("journey") line), your tvc instance of TViewController is destroyed. That is, it's dumped from memory and no code inside it can execute, because it no longer exists.
That's called "going out of scope."
So, when you try to post a notification from SViewController, there is no TViewController in existence so its code is not running to "observe" the notification.
Here is a very simple example of multiple view controllers observing a notification:
When you push from ViewController to TViewController, you get a new instance of TViewController and the original instance of ViewController still exists.
When you push from TViewController to SViewController, you get a new instance of SViewController and both ViewController and TViewController still exist.
When you tap the "Post" button, ViewController and TViewController will each execute the func you assigned to observe the notification.
When you then go Back to TViewController, you'll see its label text has changed, and when you go back to ViewController you'll see its label text has also changed.
Note that if you then push to TViewController again, you get a new instance and its label will be back at its original text.
extension Notification.Name{
static var fnote = Notification.Name("fnote")
}
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(fhandle(notification:)), name: .fnote, object: nil)
print("journey")
}
#objc func fhandle(notification:Notification) {
print("Got it!")
label.text = "Haii welcome to the hyderabad"
}
}
class TViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(handler(notification:)), name: .fnote, object: nil)
print("happy")
}
#objc func handler(notification:Notification) {
print("jackson")
label2.text = "Hurray"
}
}
class SViewController: UIViewController {
#IBOutlet var infoLabel: UILabel!
#IBAction func click(_ sender: Any) {
NotificationCenter.default.post(name: .fnote, object: nil)
infoLabel.text = "Notification Posted"
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
let storyboad = UIStoryboard(name: "Main", bundle: nil)
let tvc = storyboad.instantiateViewController(identifier: "1") as! TViewController
let _ = tvc.view
print(tvc.isViewLoaded)
This is not the way to test the notification. Push your "TViewController" controller from the "ViewController" controller. Then post notification from your target controller.

How to use observer ? Why my observer is not working here?

There are two view controller and two view controller class in my project. I want to change the first view controller background colour from the second view controller using notification and observer. But it's not working.
I have noticed that the "changeViewControllerColor(_:)" method is not calling.
First View Controller:
import UIKit
let colorChangeNotificationKey = "changeFirstVcColor"
class FirstViewController: UIViewController {
let notiName = Notification.Name(rawValue: colorChangeNotificationKey)
deinit {
NotificationCenter.default.removeObserver(self)
}
override func viewDidLoad() {
super.viewDidLoad()
observer()
}
func observer() {
NotificationCenter.default.addObserver(self, selector: #selector(FirstViewController.changeViewControllerColor(_:)), name: self.notiName, object: self)
}
#objc func changeViewControllerColor(_: NSNotification) {
self.view.backgroundColor = UIColor.white
}
#IBAction func button(_ sender: UIButton) {
let vc = storyboard?.instantiateViewController(identifier: "secondViewController") as! SecondViewController
navigationController?.pushViewController(vc, animated: true)
}
}
Second View Controller:
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
label.text = "First VC colour is white now"
let notiName = Notification.Name(rawValue: colorChangeNotificationKey)
NotificationCenter.default.post(name: notiName, object: nil)
}
}
When you are adding your observer, you are passing it the object self.
You probably want to pass it a nil.
From the documentation:
anObject
that is, only notifications sent by this sender are delivered to the
observer.
If you pass nil, the notification center doesn’t use a notification’s
sender to decide whether to deliver it to the observer.The object whose notifications the observer wants to receive;
So the only thing that it will accept notifications from is itself, which is not likely what you want.
Also, I agree with Harish, you should just use a delegate.
In SecondViewController:
NotificationCenter.default.post(name: Notification.Name("changeFirstVcColor"), object: nil)
In FirstViewController:
NotificationCenter.default.addObserver(self, selector: #selector(self.methodOfReceivedNotification(notification:)), name: Notification.Name("changeFirstVcColor"), object: nil)
#objc func methodOfReceivedNotification(notification: Notification) {}

Problem while passing data between multiple view controllers using push pop navigation

My work is
1 .Passing data from first view controller -> second view controller using pushViewController: Success
2 .Passing data from second view controller -> First View Controller using popViewController: Success //used delegate protocol for return data
3 .Passing data from third view controller -> First View Controller : Error
Code :
ViewController.swift
import UIKit
class ViewController: UIViewController,secondViewDelegate,thirddelegate {
#IBOutlet var Firstoutput: UILabel!
#IBOutlet var inputField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
func popdata(value: String) { //second view controller delegate protocol defintions
Firstoutput.text = "\(value)"
}
func thirdView(datas:String) //third view controller delegate protocol definition
{
print(datas)
}
#IBAction func pushBtn(_ sender: Any) { //first view button action
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vcFirst = storyboard.instantiateViewController(withIdentifier: "second") as! SecondViewController //secondview
vcFirst.Secondtext = inputField.text
vcFirst.delegate = self //second view delegate intiate
let vcThird = storyboard.instantiateViewController(withIdentifier: "third") as! ThirdViewController //third view
vcThird.thirddelegate = self //third view delegate intiate
navigationController?.pushViewController(vcFirst, animated: true)
}
}
SecondViewController.swift
import UIKit
//protocol for passing data when pop
protocol secondViewDelegate {
func popdata(value:String)
}
class SecondViewController: UIViewController {
#IBOutlet var secondOutputField: UILabel!
#IBOutlet var secondInputField: UITextField!
var Secondtext:String!
var delegate:secondViewDelegate!
override func viewDidLoad() {
super.viewDidLoad()
secondOutputField.text = Secondtext
}
//popping back to first view controller using data
#IBAction func popBtn(_ sender: Any) {
if delegate?.popdata != nil
{
delegate.popdata(value: secondInputField.text!)
}
navigationController?.popViewController(animated: true)
}
//pushing to third view controller
#IBAction func thirdPageBtn(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vcThird = storyboard.instantiateViewController(withIdentifier: "third") as! ThirdViewController
navigationController?.pushViewController(vcThird, animated: true)
}
}
ThirdViewController.swift
import UIKit
protocol thirdviewDelegate //delegate to pass data from thirs view to first view
{
func thirdView(datas:String)
}
class ThirdViewController: UIViewController {
var thirddelegate:thirdviewDelegate!
#IBOutlet var thirdTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad
}
#IBAction func thirdViewInvoke(_ sender: Any) {
if thirddelegate?.thirdView != nil
{
thirddelegate.thirdView(datas: thirdTextField.text!)
}
}
}
OutPut Screenshot :
Only passing data from third to first view controller is not working help me to solve this issue...Thanking you
You can use NotificationCenter to receive data.
On press of your Get Data button in your ThirdViewController, post your notification.
let myDataToPass : [String: Any] = ["myData" : yourStringValue]
NotificationCenter.default.post(name: Notification.Name("getDataPressed"), object: myDataToPass)
In your FirstViewController, add observer in viewDidLoad that will listen for the notification:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(receiveInitialDetails(notification:)), name: Notification.Name("getDataPressed"), object: nil)
}
//once the notification is received, this function will be called and you can get the data that you passed in thirdVC using notification.userInfo
#objc func receiveInitialDetails(notification : Notification) {
let data = notification.userInfo as! [String:Any]
let yourStringValue = data["myData"] as! String
// you have your data here. downcast it to your desired data type.
}
you can implement it by using notification center
below I give a small example how I pass imageDataDict in my project
let imageDataDict:[String: UIImage] = ["image": image]
// Post a notification
NSNotificationCenter.defaultCenter().postNotificationName(notificationName, object: nil, userInfo: imageDataDict)
// Register to receive notification in your class
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.showSpinningWheel(_:)), name: notificationName, object: nil)
// handle notification
func showSpinningWheel(notification: NSNotification) {
if let image = notification.userInfo?["image"] as? UIImage {
// do something with your image
}
}
You can save the third view controller data to UserDefaults. Get data from User Defaults at first View controller :
`UserDefaults.standard.set(yourData, forKey: "yourDataKey")` // at third view controller
`UserDefaults.standart.string(forKey: "yourDataKey")` //first view controller
Hope it helps...
Reason your code not working:
As soon as you move to second view controller , your third view controller is deallocated from memory. Below line has no meaning in first view controller
let vcThird = storyboard.instantiateViewController(withIdentifier: "third") as! ThirdViewController //third view
vcThird.thirddelegate = self
You move to second view controller , third view controller is set to nil.
Either you can use NSNotificationCenter to move data from third to first.
If you want to go with protocols, than make below code change.
Code : ViewController.swift
Remove the code:
let vcThird = storyboard.instantiateViewController(withIdentifier: "third") as! ThirdViewController //third view
vcThird.thirddelegate = self
ThirdViewController.swift
protocol thirdviewDelegate //delegate to pass data from thirs view to first view
{
func thirdView(datas:String)
}
class ThirdViewController: UIViewController {
var thirddelegate:thirdviewDelegate?
#IBOutlet var thirdTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func thirdViewInvoke(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let firstView = storyboard.instantiateViewController(withIdentifier: "first") as! ViewController //third view
self.thirddelegate = firstView
self.thirddelegate?.thirdView(datas: thirdTextField.text ?? "default value")
self.navigationController?.popToRootViewController(animated: true)
}
}

How to hide a UILabel from another UIViewController using Notifications and Observer

I have 2 UIViewControllers and I try to hide an UILabel from the second UIViewController using Notifications and Observer.
Is the first time when I use this design pattern and I'm a little bit confused. What I'm doing wrong ?
I want to specify that I'm getting the message from that print for the first time only when I click the back button from the second ViewController.
And after that I'm getting the message normal when I click Go Next but the UILabel is not hidden or colour changed.
Here is my code for first UIViewController:
class ReviewPhotosVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.post(name: Notification.Name("NotificationOfReviewMode"), object: nil)
}
#IBAction func goNextTapped(_ sender: UIButton) {
let fullscreenVC = storyboard?.instantiateViewController(withIdentifier: "FullscreenPhoto") as! FullscreenPhotoVC
self.present(fullscreenVC, animated: true, completion: nil)
}
}
Here is my code for second UIViewController:
class FullscreenPhotoVC: UIViewController {
#IBOutlet weak var customLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(hideCustomLabel),
name: Notification.Name("NotificationOfReviewMode"),
object: nil)
}
#IBAction func goBackTapped(_ sender: UIButton) {
let reviewPhotosVC = storyboard?.instantiateViewController(withIdentifier: "ReviewPhotos") as! ReviewPhotosVC
self.present(reviewPhotosVC, animated: true, completion: nil)
}
#objc func hideCustomLabel(){
customLabel.isHidden = true
customLabel.textColor = .red
print("My func was executed.")
}
}
Here is my Storyboard:
Thanks if you read this.
The problem is that you are posting the notification before the next controller is initialised and has started observing. Also, there is no need for the notification you can do it directly. In this case I have used an extra variable shouldHideLabel as you cannot call the function hideCustomLabel() directly because this will lead to crash as the outlets are only initialised after view is loaded.
class ReviewPhotosVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//NotificationCenter.default.post(name: Notification.Name("NotificationOfReviewMode"), object: nil)
}
#IBAction func goNextTapped(_ sender: UIButton) {
let fullscreenVC = storyboard?.instantiateViewController(withIdentifier: "FullscreenPhoto") as! FullscreenPhotoVC
fullscreenVC.shouldHideLabel = true
self.present(fullscreenVC, animated: true, completion: nil)
}
}
class FullscreenPhotoVC: UIViewController {
var shouldHideLabel = false
#IBOutlet weak var customLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if shouldHideLabel {
hideCustomLabel()
}
/*
NotificationCenter.default.addObserver(self,
selector: #selector(hideCustomLabel),
name: Notification.Name("NotificationOfReviewMode"),
object: nil)
*/
}
#IBAction func goBackTapped(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
#objc func hideCustomLabel() {
customLabel.isHidden = true
customLabel.textColor = .red
print("My func was executed.")
}
}

change label from another viewcontroller on swift

I want to change label from another viewController.
First viewcontroller is MenuController. Second one is LoginViewController.
I want to change MenuController's Label.text from LoginViewController.
In LoginViewController:
let viewController = MenuController()
viewController.changeLabel("logout")
In MenuController:
class MenuController: UITableViewController {
var attractionImages = [String]()
var attractionNames = [String]()
var webAddresses = [String]()
#IBOutlet weak var loginLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
loginLabel.text = "Login"
print(loginLabel.text)
}
func changeLabel(Log: String)O {
self.loginLabel.text = log
print (log)
}
But an error occur.
fatal error: unexpectedly found nil while unwrapping an Optional value
How can I solve it?
Thanks for your help.
Another way to achieve that is you can use NSNotificationCenter. Blow is the example for that:
In your MenuController add this code:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "refreshLbl:", name: "refresh", object: nil)
}
Also add this helper method:
func refreshLbl(notification: NSNotification) {
print("Received Notification")
lbl.text = "LogOut"
}
Now in your LoginViewController your back button action will look like:
#IBAction func back(sender: AnyObject) {
NSNotificationCenter.defaultCenter().postNotificationName("refresh", object: nil, userInfo: nil)
self.dismissViewControllerAnimated(true, completion: nil)
}
Now when ever you press back button from LoginViewController your refreshLbl method will call from MenuController.
For more info refer THIS example.
Swift 3 version:
In your MenuController (where the label needs to be changed) add this code:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(refreshLbl),
name: NSNotification.Name(rawValue: "refresh"),
object: nil)
}
Also add this helper method:
#objc func refreshLbl() {
print("Received Notification")
lbl.text = "LogOut"
}
Now in your LoginViewController your back button action will look like:
#IBAction func backButton(_ sender: Any) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "refresh"), object: nil)
// Any additional code...
}
Now when ever you press back button from LoginViewController your refreshLbl() method will call from MenuController.

Resources