Passing data between Tab Bar Controllers with NSNotificationCenter in Swift - ios

I'm trying to pass data between tab bar controllers for the firts time, but it doesn't work.
First Controller:
class FirstViewController: UIViewController {
#IBOutlet weak var sentNotificationLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateNotificationSentLabel:", name: mySpecialNotificationKey, object: nil)
}
#IBAction func notify() {
NSNotificationCenter.defaultCenter().postNotificationName(mySpecialNotificationKey, object: nil, userInfo:["message":"Something"])
}
func updateNotificationSentLabel(notification:NSNotification) {
let userInfo:Dictionary<String,String!> = notification.userInfo as Dictionary<String,String!>
let messageString = userInfo["message"]
sentNotificationLabel.text = messageString
}
}
Here it works ok, the sentNotificationLabel.text is "Something"
Second Controller is similiar, but he's not receiving any notification.
class SecondViewController: UIViewController {
#IBOutlet weak var notificationLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateLabel:", name: mySpecialNotificationKey, object: nil)
}
func updateLabel(notification:NSNotification) {
let userInfo:Dictionary<String,String!> = notification.userInfo as Dictionary<String,String!>
let messageString = userInfo["message"]
notificationLabel.text = messageString
}
}
What I am doing wrong? How to change that?

The problem occurs because you are sending the message before the second view controller is registered.
NSNotificationCenter doesn't support sticky notifications - in other words, the message won't be cached. If no-one is listening at the very moment the notification is sent, it's just gone.
The easiest fix is to register your SecondViewController for notification in initialiser. However, than the UILabel is not yet loaded - we are before viewDidLoad callback. The solution is to cache the received message locally and use it to set the label text when it's ready.
class SecondViewController: UIViewController {
#IBOutlet weak var notificationLabel: UILabel!
var cachedMessage : String? = nil
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateLabel:", name: mySpecialNotificationKey, object: nil)
}
override func viewDidLoad() {
if let cachedMessage = cachedMessage {
notificationLabel.text = cachedMessage
}
}
func updateLabel(notification:NSNotification) {
let userInfo:Dictionary<String,String!> = notification.userInfo as Dictionary<String,String!>
let messageString = userInfo["message"]
if let notificationLabel = notificationLabel {
notificationLabel.text = messageString
} else {
cachedMessage = messageString
}
}
}
Since controllers are being initialised before they are being displayed, the message should be delivered properly.

Simply add observer in init method
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "addBadge", name: "addBadge", 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)
}
}

Notification not triggering when view controller updated on Navigation stack changed

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)

How to change language (localisation) within the app in swift 5?

I am trying to localise iOS app which is developed in Swift 5. I have done with all localisation things in code as well as in storyboard. But I am not sure how to change language within the app when i click on Language Button.
Is this possible to change app language within app? if yes How?
Please suggest best possible way to do same
I just did a similar implementation. Glad you asked and I saw this. Here is my implementation. You can modify.
enum Language: String, CaseIterable {
case english, german
var code: String {
switch self {
case .english: return "en"
case .german: return "de"
}
}
static var selected: Language {
set {
UserDefaults.standard.set([newValue.code], forKey: "AppleLanguages")
UserDefaults.standard.set(newValue.rawValue, forKey: "language")
}
get {
return Language(rawValue: UserDefaults.standard.string(forKey: "language") ?? "") ?? .english
}
}
static func switchLanguageBetweenEnglishAndGerman() {
selected = selected == .english ? .german : .english
}
}
Now you just need to call Language.selected == .german and reload the views.
To change localization throughout the app. For that, You need to follow the below step.
Create a Parent class of every UIViewController and define setupLocasitation method for further usage.
ParentViewController.swift
class ParentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func setupLocasitation(){
}
}
All other class of UIViewController should be a subclass of ParentViewController and override setupLocasitation method
ViewController1.swift
class ViewController1: ParentViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupLocasitation()
}
override func setupLocasitation() {
super.setupLocasitation()
print("Your localisation specifi code here...")
}
}
ViewController2.swift
class ViewController2: ParentViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupLocasitation()
}
override func setupLocasitation() {
super.setupLocasitation()
print("Your localisation specifi code here...")
}
}
ChangeLanguageVC.swift
You need to grab all instances of ParentViewController and force-fully call the setupLocasitation method.
class ChangeLanguageVC: ParentViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupLocasitation()
}
#IBAction func btnChangeLanguageTap(){
//Code for your language changes here...
let viewControllers = self.navigationController?.viewControllers ?? []
for vc in viewControllers{
if let parent = vc as? ParentViewController{
parent.setupLocasitation()
}
}
}
}
//
// LanguageExtensions.swift
// Flourish
//
// Created by Janko on 11/11/2020.
//
import Foundation
import UIKit
let languageKey = "languageKey"
var language : Int {
switch UserDefaults.standard.string(forKey: languageKey) {
case "en":
return 0
case "dutch":
return 1
default:
return 0
}
}
extension String {
func localizedLanguage()->String?{
var defaultLanguage = "en"
if let selectedLanguage = UserDefaults.standard.string(forKey: languageKey){
defaultLanguage = selectedLanguage
}
return NSLocalizedString(self, tableName: defaultLanguage, comment: "")
}
}
class LanguageLabel: UILabel{
required init?(coder: NSCoder) {
super.init(coder: coder)
NotificationCenter.default.addObserver(self, selector: #selector(updateUI), name: AppNotification.changeLanguage, object: nil)
}
#IBInspectable var localizedLanguage: String? {
didSet{
updateUI()
}
}
#objc func updateUI(){
if let string = localizedLanguage {
text = string.localizedLanguage()
}
}
}
class LanguageButton: UIButton{
required init?(coder: NSCoder) {
super.init(coder: coder)
NotificationCenter.default.addObserver(self, selector: #selector(updateUI), name: AppNotification.changeLanguage, object: nil)
}
#IBInspectable var localizedLanguage: String? {
didSet{
updateUI()
}
}
#objc func updateUI(){
if let string = localizedLanguage {
setTitle(string.localizedLanguage(), for: .normal)
}
}
}
struct AppNotification{
static let changeLanguage = Notification.Name("changeLanguage")
}
extension UIViewController{
func changeLanguage(){
let alert = UIAlertController(title: "Change Language", message: "Change it", preferredStyle: .alert)
let actionEnglish = UIAlertAction(title: "English", style: .default) { (action) in
UserDefaults.standard.setValue("en", forKey: languageKey)
NotificationCenter.default.post(name: AppNotification.changeLanguage , object: nil)
}
let actionMontenegrin = UIAlertAction(title: "Montenegrinish", style: .default) { (action) in
UserDefaults.standard.setValue("dutch", forKey: languageKey)
NotificationCenter.default.post(name: AppNotification.changeLanguage , object: nil)
}
alert.addAction(actionEnglish)
alert.addAction(actionMontenegrin)
present(alert, animated: true, completion: nil)
}
}

Custom init UIViewController query

I am hoping you can help me understand why the below code segment works and the other does not. I am wanting to create a custom initialiser for my UIViewController which has a custom nib file I have created.
My issue is that I want to understand why in the below code the references to newMember and facebookLogin are retained when I hit the viewDidLoad method but in the other segment of code they are not? Can anyone shed some light as to why this would be the case?
Working Code Block
class RegistrationFormViewController: MiOSBaseViewController
{
var newMember:Member!
var facebookLogin: Bool = false
init(member: Member, facebookLogin: Bool = false) {
self.newMember = member
self.facebookLogin = facebookLogin
super.init(nibName: "RegistrationFormViewController", bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(nibName: "RegistrationFormViewController", bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let view = self.view as! RegistrationFormView
view.loadViewWith(member: newMember)
view.customNavBarView.backActionBlock = {
self.newMember.deleteEntity(MiOSDataContext.sharedInstance.managedObjectContext)
_ = self.navigationController?.popViewController(animated: true)
return
}
}
}
Broken Code Block
class RegistrationFormViewController: MiOSBaseViewController
{
var newMember:Member!
var facebookLogin: Bool = false
init(member: Member, facebookLogin: Bool = false) {
self.newMember = member
self.facebookLogin = facebookLogin
super.init(nibName: "RegistrationFormViewController", bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
let view = self.view as! RegistrationFormView
view.loadViewWith(member: newMember)
view.customNavBarView.backActionBlock = {
self.newMember.deleteEntity(MiOSDataContext.sharedInstance.managedObjectContext)
_ = self.navigationController?.popViewController(animated: true)
return
}
}
}
Thanks,
Michael

How to get Notification from standalone class (Swift 3)

I want to call method in a class as soon as observer get Notification in another one. The problem is that I cannot call one class from another, because I will get recursion call then.
1) Controller class with Player instance:
// PlayerController.swift
// player
import UIKit
import MediaPlayer
class NowPlayingController: NSObject {
var musicPlayer: MPMusicPlayerController {
if musicPlayer_Lazy == nil {
musicPlayer_Lazy = MPMusicPlayerController.systemMusicPlayer()
let center = NotificationCenter.default
center.addObserver(
self,
selector: #selector(self.playingItemDidChange),
name: NSNotification.Name.MPMusicPlayerControllerNowPlayingItemDidChange,
object: musicPlayer_Lazy)
musicPlayer_Lazy!.beginGeneratingPlaybackNotifications()
}
return musicPlayer_Lazy!
}
//If song changes
func playingItemDidChange(notification: NSNotification) {
//somehow call updateSongInfo() method from 2nd class
}
//Get song metadata
func getSongData() -> (UIImage, String?, String?) {
let nowPlaying = musicPlayer.nowPlayingItem
//...some code
return (albumImage, songName, artistAlbum)
}
func updateProgressBar() -> (Int?, Float?){
let nowPlaying = musicPlayer.nowPlayingItem
var songDuration: Int?
var elapsedTime: Float?
songDuration = nowPlaying?.value(forProperty: MPMediaItemPropertyPlaybackDuration) as? Int
elapsedTime = Float(musicPlayer.currentPlaybackTime)
return(songDuration, elapsedTime)
}
}
2) View controller which should be updated when Player Controller get notification
// MainViewController.swift
// player
import UIKit
class MainViewController: UIViewController {
let playerController = PlayerController()
#IBOutlet weak var albumView: UIImageView!
#IBOutlet weak var songLabel: UILabel!
#IBOutlet weak var artistAlbum: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//Start updating progress bar
Timer.scheduledTimer(timeInterval: 0.5,
target: self,
selector: #selector(MainViewController.updateProgressBar),
userInfo: nil,
repeats: true)
}
private func updateSongInfo(){
(albumView.image!, songLabel.text, artistAlbum.text) = playerController.getSongData()
}
private func updateProgressBar(){
(progressBar.maximumValue, progressBar.value) = playerController.playingItemProgress()
}
}
Solution for Swift 3:
In NowPlayingController:
let newSongNotifications = NSNotification(name:NSNotification.Name(rawValue: "updateSongNotification"), object: nil, userInfo: nil)
func playingItemDidChange(notification: NSNotification) {
NotificationCenter.default.post(newSongNotifications as Notification)
}
And in other controller:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.updateSongInfo), name: NSNotification.Name(rawValue: "updateSongNotification"), object: nil)
}
You can post a notification from within your custom object where you need it:
let notification = NSNotification(name:"doSomethingNotification", object: nil, userInfo: nil)
NotificationCenter.defaultCenter.postNotification(notification)
And then in your other view controller in which you want to execute something in response to this notification, you tell it to observe the notification in viewDidLoad(). The selector you pass in is the method you want to be executed when the notification is received.
override func viewDidLoad(){
super.viewDidLoad()
NotificationCenter.addObserver(self, selector: #selector(self.doSomething), name: "doSomethingNotification", object: nil)
}
You can use delegate method to update MainViewController

Resources