Update Label On Another View - ios

I have two views with a label on one of them. On the second view, there is a button. What I want to achieve here is to be able to press the button and it updates the label on the first view. How do I do that? I can't access the IBOutlet from the second view. Is there something that I have to do to the IBOutlet to make it public etc?

You can use NSNotificationCenter for that.
First of all in your viewDidLoad method add this code in your firstViewController class:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "refreshlbl:", name: "refresh", object: nil)
which will addObserver when your app loads.
And add this helper method in same viewController.
func refreshlbl(notification: NSNotification) {
lbl.text = "Updated by second View" //Update your label here.
}
After that in your secondViewController add this code when you dismiss your view:
#IBAction func back(sender: AnyObject) {
NSNotificationCenter.defaultCenter().postNotificationName("refresh", object: nil, userInfo: nil)
self.dismissViewControllerAnimated(true, completion: nil)
}
Now when you press back button from secondView then refreshlbl method will call from firstView.

use custom delegate method create a delegate in second view and access that view delegate function in first view. or use NSNotification or use NSUserdefaults

Related

Swift - access UIButton inside ContainerView in another ViewController

I am having a problem accessing my UIButton that's inside a ContainerView from another ViewController.
If the user taps the button a SubView should appear inside of my ViewController. I tried dragging the button in my ViewController file to create an #IBAaction func tapped() but that is not working.
It only works inside of the ContainerView.swift file. But I can not create my Subview there...
I hope you get my problem, I am grateful for every help!
import UIKit
class ContainerViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func addButtonTapped(_ sender: Any) {
print("add Button")
}
You can reach your ViewController inside your ContainerView using its parent property. So, inside your ContainerView class:
if let parentVC = self.parent as? ViewController {
parentVC.showSubView() // your method to show the sub view.
}
You can achieve this using NotificationCenter and Delegates. Here is how you can achieve it with NotificationCenter
In your ContainerViewController you can post notification on button click
#IBAction func yourBtnTap(sender : UIButton) {
NotificationCenter.default.post(name: Notification.Name("myBtnTapped"), object: nil)
}
In your CurrentViewController you have to first register to receive this notification. So in your viewDidLoad, do this:
NotificationCenter.default.addObserver(self, selector: #selector(self.myBtnTappedAction(notification:)), name: Notification.Name("myBtnTapped"), object: nil)
Now in your CurrentViewContoller create myBtnTappedAction function that will be called whenever the button is tapped.
#objc func myBtnTappedAction(notification : Notification) {
// now you can perform all your actions here. this function will be called everytime the button in the container view is tapped.
}

addObserver selector function is never called in embedded view controller (Swift 3)

I have my storyboard set up with a main ViewControllerand a NavigationController with a secondary ViewController embedded. The main ViewController and NavigationController are connected with a show segue that is triggered on a button press. The photo attached shows my storyboard.
When the button is clicked, the label in LabelViewController should change, and the show segue should trigger and show the NavigationViewController (with LabelViewController embedded). The last half works, and the NavigationViewController is shown, but the label's value never changes.
To change the label's value, I am using NotificationCenter's addObserver and post. While there are other methods, I need to use NotificationCenter to pass through data (userData).
Code
Inside of the first ViewController I have this single line of code inside of an IBAction that triggers on the button's press.
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "changeLabelValue"), object: nil, userInfo: ["labelValue": "Changed value"])
Then, inside of LabelViewController's viewDidLoad I have another line of code for the observer.
NotificationCenter.default.addObserver(self, selector: #selector(changeLabelValue(_:)), name: NSNotification.Name(rawValue: "changeLabelValue"), object: nil)
The selector function, changeLabelValue looks like this.
func changeLabelValue(_ notification: NSNotification) {
label.text = notification.userInfo?["value"] as? String
}
Problem
As said above, the show segue triggers and I am shown the NavigationViewController, but the selector function (changeLabelValue) inside of LabelViewController never gets called.
Your function is never called because it is only resisted to receive function calls in the second view controller's viewDidLoad. However, when you press your button in your first view controller, your second view controller hasn't been loaded.
To correctly pass data, you need to use prepareForSegue. So in your first view controller, you need to first check if your segue destination is an UINavigationController because your second view controller is embedded inside. If so, check if the topViewController of your navigation controller is your SecondViewController. If both are true, pass the data and it will be available in viewDidLoad section of your SecondViewController. Sample code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationViewController = segue.destination as? UINavigationController {
if let secondViewControoler = destinationViewController.topViewController as? SecondViewController {
secondViewControoler.yourProperty = self.yourLabel.text
}
}
}
UPDATE
The following is not valid in Swift 3. Check out #FangmingNing's answer.
You would need to add #objc to your changeLabelValue function as the observer must inherit from an Objective-C NSObject.
#objc func changeLabelValue(_ notification: NSNotification) {
label.text = notification.userInfo?["value"] as? String
}

Side menu hide issue

I have a table view as a side menu on a ViewController xib that has to be added as a subview by clicking on a particular menu button.A logout button exists on side menu bottom.I want to perform popToRootViewController on click of logos button.
I am adding menu like this :
menuViewController?.view.frame = self.transparentView.frame
menuViewController?.didMove(toParentViewController: self)
menuViewController?.view.frame.size.width = (mapView.frame.size.width)/1.5
menuViewController?.view.tag=10
self.transparentView.addSubview((menuViewController?.view)!)
if you have single Navigation Controller chain in your application you can easily use below code to navigate on RootViewController.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if let navigationController = appDelegate.window?.rootViewController as? UINavigationController {
navigationController.popToRootViewController(animated: true)
}
Hope this will helps.
Use NotificationObserver to achieve this goal.
Objective-C
In your mainViewController's viewDidLoad method write below code.
[[NSNotificationCentre defaulCentre] addObserver:self withName:"LoggoutNotificationMessage" selector:#selector(shouldLogout:) withObject:nil];
now add this method in this class
-(void) shouldLogout {
//Code to Pop to Root VC
}
Now in your ViewController class where you have implemented side menu's tableView delegate and datasource.
In didSelectRowAtIndexPath: method write below line.
[[NSNotificationCentre defaultCentre] postNotification:"LoggoutNotificationMessage" withObject:nil]
Swift 3.0
// in main ViewController.swift's viewDidLoadMethod
NotificationCenter.default.addObserver(self, selector: #selector(shoudLogout), name: NSNotification.Name(rawValue: "LogoutNotificationMessage"), object: nil)
#objc func shoudLogout() {
//Pop to root vc here
}
Now in your ViewController class where you have implemented side menu's tableView delegate and datasource.
In didSelectRowAtIndexPath: method write below line.
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "LogoutNotificationMessage"), object: nil)

viewWillAppear not called when going back on a UINavigationController

I have some code I call that changes the language in the viewWillAppear section of a viewcontroller inside a navigation controller.
When I hit the back button the language change doesn't take place even though I have code for it to in the viewWillAppear. The only time it switches is when I hit back all the way to the original screen and then start moving forward it changes. Is there any way to have the function in the viewWillAppear work?
Here is my code, I'm using a language changing pod:
//MARK: Language change
//used to change language text for imediate screens
func setText(){
locationsLabel.text = "Locations".localized()
languageLabel.text = "Languages".localized()
termsOfUseLabel.text = "Terms of Use".localized()
privacyPolicyLabel.text = "Privacy Policy".localized()
pushNotificationsLabel.text = "Push Notifications".localized()
contactUsLabel.text = "Contact Us".localized()
}
// Changes text to current language
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "setText", name: LCLLanguageChangeNotification, object: nil)
}
// Remove the LCLLanguageChangeNotification on viewWillDisappear
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
The viewWillAppear method is only adding a notification observer. The observer is removed in viewWillDisappear. This means that setText will only be called if the LCLLanguageChangeNotification notification is sent while the view is visible.
The update stops as soon as the view goes off-screen due to the navigation behaviour.
To ensure that the text is updated, you also need to call setText inside viewWillAppear:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
setText()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "setText", name: LCLLanguageChangeNotification, object: nil)
}
Implement navigationcontroller delegate methods
navigationController:willShowViewController:animated:
navigationController:didShowViewController:animated:
I have created this little class that solves this problem.
Just set it as a delegate of your navigation controller, and implement simple one or two methods in your view controller - that will get called when the view is about to be shown or has been shown via NavigationController
Here's the GIST showing the code

How to reload tableview from another view controller in swift

When I dismiss a modal view controller I want the tableview to update, I am using the form sheet presentation style on iPad so the viewWillAppear and the viewDidAppear methods will not work
You can do this:
In your tableView Controller:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "load"), object: nil)
}
#objc func loadList(notification: NSNotification){
//load data here
self.tableView.reloadData()
}
Then in the other ViewController :
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
Swift 3 version code:
In your first view controller:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "load"), object: nil)
}
func loadList(){
//load data here
self.tableView.reloadData()
}
In your second view controller:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
I find the segue approach more elegant.
Let's say that we have ViewControllerA and a ViewControllerB. We are at ViewControllerB and we want from ViewControllerB to jump straight back to ViewControllerA and update the table view in ViewControllerA.
In ViewControllerA add the following action in your ViewController class:
#IBAction func unwindToViewControllerA(segue: UIStoryboardSegue) {
DispatchQueue.global(qos: .userInitiated).async {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
Yes, this code goes in the ViewController of the ViewController you want to go back to!
Now, you need to create an exit segue from the ViewControllerB's storyboard (StoryboardB). Go ahead and open StoryboardB and select the storyboard. Hold CTRL down and drag to exit as follows:
You will be given a list of segues to choose from including the one we just created:
You should now have a segue, click on it:
Go in the inspector and set a unique id:
In the ViewControllerB at the point where you want to dismiss and return back to ViewControllerA, do the following (with the id we set in the inspector previously):
self.performSegue(withIdentifier: "yourIdHere", sender: self)
Now, whenever you use the segue to return back to ViewControllerA, the ViewControllerA will update the TableView straightaway.
Missing comma on this line, should instead be:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loadList:", name:"load", object: nil)
An alternate solution: override UIViewController's dismiss(animated:completion:) method on the view controller that manages the table view (and presents the other one modally), so you can reload the table then:
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
super.dismiss(animated: flag, completion: completion)
self.tableView.reloadData()
}
Note: This should work even if you call dismiss(animated:completion:) on the modal view controller itself (a viable alternative), because ultimately the call gets relayed to the presenting view controller.
From the method's docs:
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, UIKit asks the presenting view controller to handle the dismissal.
(emphasis mine)
You can use NotificationCenter to update your tableview.
First add observer...
NotificationCenter.default.addObserver(self, selector: #selector(doThisWhenNotify(notification:)), name: NSNotification.Name(rawValue: "load"), object: nil)
func doThisWhenNotify(notification : NSNotification) {
let info = notificatio.userInfo
//update tableview
}
Post on other ViewController
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil, userInfo: [String : Any])

Resources