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.
Related
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)
}
}
I'm applying for a junior developer position and I've got a very specific task, that already took me 3 days to complete. Sounds easy - pass data to rootViewController.
That's what I've done:
1)
private func userDefaultsToRootController() {
let input = textField.text!
defaults.set(input, forKey: "SavedLabel")
navigationController?.popViewController(animated: true)
}
private func segueToRootViewController() {
let destinationVC = MainScreen1()
let input = textField.text!
if input == "" { self.navigationController?.popToRootViewController(animated: true) }
destinationVC.input = input
navigationController?.pushViewController(destinationVC, animated: true)
}
private func popToNavigationController() {
let input = textField.text!
if let rootVC = navigationController?.viewControllers.first as? MainScreen1 {
rootVC.input = input
}
navigationController?.popToRootViewController(animated: true)
}
I've used CoreData
But here is the difficult part - I've got an email, that all these methods are not good enough and I need to use delegate and closure. I've done delegation and closures before, but when I popToRootViewController delegate method passes nil. Could you at least point where to find info about this?
** ADDED **
There are 2 View Controllers: Initial and Second one.
That's what I have in the Initial View Controller:
var secondVC = MainScreen2()
override func viewDidLoad() {
super.viewDidLoad()
secondVC.delegate = self
}
That's how I push SecondViewController
#objc private func buttonTapped(_ sender: CustomButton) {
let nextViewController = MainScreen2()
navigationController?.pushViewController(nextViewController, animated: true)
}
In SecondViewController I've got this protocol
protocol PassData {
func transferData(text: String)
}
Also a delegate:
var delegate: PassData?
This is how I go back to initial view controller
#objc private func buttonTapped(_ sender: CustomButton) {
if let input = textField.text {
print(input)
self.delegate?.transferData(text: input)
self.navigationController?.popToRootViewController(animated: true)
}
}
Back to the Initial view controller where I've implemented delegate method
extension MainScreen1: PassData {
func transferData(text: String) {
print("delegate called")
label.text = text
}
}
Delegate doesn't get called.
BASED ON YOUR EDIT:
You must set the delegate in buttonTapped
#objc private func buttonTapped(_ sender: CustomButton) {
let nextViewController = MainScreen2()
nextViewController.delegate = self // HERE WHERE YOU SET THE DELEGATE
navigationController?.pushViewController(nextViewController, animated: true)
}
You can delete the second instance and your code in viewDidLoad. That's not the instance you push.
This should point you in the right direction to use delegation and completion handler.
protocol YourDelegateName {
func passData(data:YourDataType)
}
class SecondViewController: UIViewController {
var delegate: YourDelegateName?
func passDataFromSecondViewController(){
YourCoreDataClass.shared.getCoreData { (yourStringsArray) in
self.delegate?.passData(data: yourStringsArray)
self.navigationController?.popToRootViewController(animated: true)
}
}
class InitialViewController: UIViewController, YourDelegateName {
override func viewDidLoad() {
super.viewDidLoad()
// or whenever you instantiate your SecondViewController
let secondViewController = SecondViewController()
secondViewController.delegate = self //VERY IMPORTANT, MANY MISS THIS
self.navigationController?.pushViewController(createVC, animated: true)
}
func passData(data:YourDataType){
//user your data
}
}
class YourCoreDataClass: NSObject {
static let shared = YourCoreDataClass()
func getCoreData (completion: ([String]) -> ()){
........... your code
let yourStringsArray = [String]() // let's use as example an array of strings
//when you got the data your want to pass
completion(yourStringsArray)
}
}
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.
I am trying to perform a segue that will be call form another class than ui view. The final target is that the main view will wait that an URL request is done to go to the next view.
Here my ui view:
class LoadingViewController: UIViewController {
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
CardList.retrieveAllCards()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func goToNextView() {
performSegue(withIdentifier: "segueToMainController", sender: LoadingViewController.self)
}
class ShowNext {
class func view(fromViewController: LoadingViewController) {
fromViewController.goToNextView()
}
}
}
and here how I call my segue from the other class
let task = session.dataTask(with: request){
data,response,error in
do
{
if let jsonResult = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary {
jsonResultEnd = jsonResult
//print("SUCCESS:\(jsonResult)")
LoadingViewController.ShowNext.view(fromViewController: LoadingViewController())
print("loading ended")
}
} catch let error as NSError {
print("ERROR request manager: \(error.localizedDescription)")
}
}
Here is the error
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver () has no segue with identifier 'segueToMainController''
It doesn't make any sense to instantiate a new LoadingViewController and try to pass that into your class func. What you need to do is get a reference to your existing LoadingViewController and call goToNextView() on that.
There are many ways to do this, but one way would be to pass a reference to your LoadingViewController as a variable on your other view, and when your async call finishes you can call self.loadingViewController.goToNextView().
You could also use NotificationCenter to broadcast from your view that this async call is complete, and observe that notification on your LoadingViewController and use that event to trigger the segue:
In your LoadingViewController's viewDidLoad method:
NotificationCenter.default.addObserver(self, selector: #selector(goToNextView), name: Notification.Name("loadingComplete"), object: nil)
And in your other view's async callback:
NotificationCenter.default.post(name: Notification.Name("loadingComplete"), object:
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.