NotificationCenter to pass data in Swift - ios

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)

Related

Using #objc in Swift 5 protocols

I have an issue with using #objc code in swift protocols and was wondering if there is a workaround for that.
Currently, I have code:
import UIKit
#objc
protocol TrackScreenshot {
func registerObserver()
func removeObservers()
}
extension TrackScreenshot where Self: ScreenTracking {
func registerObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(trackScreenshot), name: UIApplication.userDidTakeScreenshotNotification, object: nil)
}
func removeObservers() {
NotificationCenter.default.removeObserver(self, name: UIApplication.userDidTakeScreenshotNotification, object: nil )
}
func trackScreenshot() {
print(screenName.rawValue)
}
}
So I want to inherit the TrackScreenshot protocol and make screens easily trackable.
BUT there is an issue.
registerObserver() method on #selecor asks to add #objc to trackScreenshot method, but if I do so, Xcode complains on trackScreenshot() line and telling: #objc can only be used with members of classes, #objc protocols, and concrete extensions of classes
Is there a way to fix this?
Also tried:
NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: nil) { _ in
print(self.screenName.rawValue)
}
but it's not working, and the observer can't be removed and remains in the circle, so prints all the previous screen names when a new screen is opened.
Any help is more then welcome! Thanks in advance!
I would use the closure form of notification observation rather than a selector/method:
protocol TrackScreenshot {
func registerObserver(handler: (()->Void)?)
func removeObservers()
}
extension TrackScreenshot where Self: ScreenTracking {
func registerObserver(handler: (()->Void)?) {
NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: nil) { (notification) in
handler?()
}
}
func removeObservers() {
NotificationCenter.default.removeObserver(self, name: UIApplication.userDidTakeScreenshotNotification, object: nil )
}
}
Then your use is something like:
self.registerObserver { [weak self] in
guard let self = self else {
return
}
print("Screen shot")'
}
You can track screenshot notification using delegates too, feel free to refactor as per your need:
public protocol ScreenshotDelegate: AnyObject {
func screenshotDetected()
}
open class ScreenshotTracker: NSObject {
private weak var delegate: ScreenshotDelegate?
public init(delegate: ScreenshotDelegate) {
self.delegate = delegate
NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: OperationQueue.main) { notification in
delegate.screenshotDetected()
print("Screenshot notification")
}
}
}
ViewController Setup:
override func viewDidLoad() {
super.viewDidLoad()
let _ = ScreenshotTracker(delegate: self)
}
extension ViewController: ScreenshotDelegate {
func screenshotDetected() {
print("screenshot taken!!!")
}
}

Notification Center not working. The observer is not being called

I am trying to call a function from another class in Swift and NotificationCenter is an option to do that so I started with the addObserver.
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(toggleSideMenu), name: NSNotification.Name("callToggleSideMenu"), object: nil)
}
#objc func toggleSideMenu(){
if isMenuOpen {
sideContainer.constant = -260
} else {
sideContainer.constant = 0
}
}
And in the other class I have added the (post):
#objc func clickOnButton(button: UIButton) {
NotificationCenter.default.post(name: NSNotification.Name("callToggleSideMenu"), object: nil)
}
Everything seems ok but I do not know why it is not working. I have seen a lot of the same issue here in stackoverflow but no answer solved my issue.
Function definition is not correct. It should be :
#objc func toggleSideMenu(_ notification: Notification){
if isMenuOpen {
sideContainer.constant = -260
} else {
sideContainer.constant = 0
}
}
Call it using :
NotificationCenter.default.addObserver(self, selector: #selector(toggleSideMenu(_:)), name: NSNotification.Name("callToggleSideMenu"), object: nil)

NotificationCenter - text transfer between views

I have this code:
MainViewControler.swift:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(showAlertMessage(notification:)), name: Notification.Name("NotificationAlertMessage"), object: errorMessage.self)
}
#objc func showAlertMessage(notification: NSNotification) {
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! \(notification.title)")
}
AlertStruct.swift:
struct errorMessage{
var title: String?
var description: String?
}
Propierties.swift
func showError(){
NotificationCenter.default.post(name: Notification.Name("NotificationAlertMessage"), object: errorMessage(title: "tytuł", description: "description"))
}
override func viewDidLoad() {
super.viewDidLoad()
showError()
}
Propierties.swift is child in containerView MainViewControler.
I would like to display the function ShowAlertMessage in my MainViewControler with data sent from Propierties -> func showError ()
How to do it? My code does not want to compile / work correctly :(
Your code is almost correct...Just update below method
#objc func showAlertMessage(notification: NSNotification) {
let object = notification.object as! errorMessage
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! \(object.title)")
}
try this, tested
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(showAlertMessage(notification:)), name: Notification.Name("NotificationAlertMessage"), object: nil)
NotificationCenter.default.post(name: Notification.Name("NotificationAlertMessage"), object: nil, userInfo: ["object": ErrorMessage(title: "tytuł", description: "description")])
}
#objc func showAlertMessage(notification: NSNotification) {
if let errorMessage = notification.userInfo?["object"] as? ErrorMessage {
print("\(errorMessage.title), \(errorMessage.description)")
}
}

tableview reloadData crashes unexpected found nil while unwrapping optional value

I'm having some trouble understanding why the reloadData() line crashes with the following error 'unexpected found nil while unwrapping optional value'(also, why is it an optional?). Basically, when user taps a button it fires an API request, second VC(recipesVC) is shown and when data is retrieved from the API, in receivedRecipes method (previous VC) I want to reloadData from recipesVC (currentVC)
As my data is correctly passed to recipesVC, I don't see why reloadData() won't work on the same VC. Could you please give me a little help with this?
thank you.
override func viewDidLoad() {
super.viewDidLoad()
let recipes = Notification.Name(rawValue: "gotRecipes")
NotificationCenter.default.addObserver(self, selector: #selector(receivedRecipes),name: recipes, object: nil)
}
#objc func receivedRecipes() {
let recipesVC = storyboard?.instantiateViewController(withIdentifier: "recipesList") as! RecipesViewController
recipesVC.recipesList = request.recipeDetails
recipesVC.recipes.reloadData()
}
#objc func receivedRecipes() {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "recipes"), object: nil, userInfo: ["data":request.recipeDetails])
}
In your currentVC
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(receivedRecipes(notification:)),name: NSNotification.Name(rawValue: "recipes"), object: nil)
}
#objc func receivedRecipes(notification: Notification) {
recipesList = notification.userInfo
recipes.reloadData()
}
Add validation to your data:
func receivedRecipes() {
guard let details = request.recipeDetails else { return }
let recipesVC = storyboard?.instantiateViewController(withIdentifier: "recipesList") as! RecipesViewController
recipesVC.recipesList = details
recipesVC.recipes.reloadData()
}
The next tip, you can update your notification and get recipes from the notification.object, but previously you should paste them into:
let parsedRecipes = Recipes()
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "gotRecipes"), object: parsedRecipes, userInfo: nil)
Also show recipesVC after it initialization with:
self.present(...) or self.navigationController?.show(...)

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

Resources