This question might be asked already about hiding buttons, but I was wondering if I could just click a button which would affect the variables in another view controller. For example, I have firstViewController and endViewController. There's a button in endViewController that the user presses which should change a variable in the firstViewController. Is there a way to access the endViewController button from the firstViewController?
Edit
I haven't tried much so far except control clicking the endViewController button into the firstViewController (which didn't work).
class firstViewController: UIViewController {
#IBAction func nextButton(_ sender: Any) { //button that sits in endViewController
}
}
You can use the DELEGATE PATTERN to pass data back:
Here's a little help on delegates between two view controllers:
Step 1: Make a protocol in the UIViewController that you will be removing/will be sending the data.
protocol FooTwoViewControllerDelegate:class {
func myVCDidFinish(_ controller: FooTwoViewController, text: String)
}
Step2: Declare the delegate in the sending class (i.e. UIViewcontroller)
class FooTwoViewController: UIViewController {
weak var delegate: FooTwoViewControllerDelegate?
[snip...]
}
Step3: Use the delegate in a class method to send the data to the receiving method, which is any method that adopts the protocol.
#IBAction func saveColor(_ sender: UIBarButtonItem) {
delegate?.myVCDidFinish(self, text: colorLabel.text) //assuming the delegate is assigned otherwise error
}
Step 4: Adopt the protocol in the receiving class
class ViewController: UIViewController, FooTwoViewControllerDelegate {
Step 5: Implement the delegate method
func myVCDidFinish(_ controller: FooTwoViewController, text: String) {
colorLabel.text = "The Color is " + text
controller.navigationController.popViewController(animated: true)
}
Step 6: Set the delegate in the prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mySegue" {
let vc = segue.destination as! FooTwoViewController
vc.colorString = colorLabel.text
vc.delegate = self
}
}
And that should work. This is of course just code fragments, but should give you the idea. For a long explanation of this code you can go over to my blog entry here:
segues and delegates
If you are interested in what's going on under the hood with a delegate I did write on that here:
under the hood with delegates
original answer
First View Controller
The code for the First View Controller is
import UIKit
class FirstViewController: UIViewController, DataEnteredDelegate {
#IBOutlet weak var label: UILabel!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showSecondViewController" {
let secondViewController = segue.destinationViewController as! SecondViewController
secondViewController.delegate = self
}
}
func userDidEnterInformation(info: String) {
label.text = info
}
}
Note the use of our custom DataEnteredDelegate protocol.
Second View Controller and Protocol
The code for the second view controller is
import UIKit
// protocol used for sending data back
protocol DataEnteredDelegate: class {
func userDidEnterInformation(info: String)
}
class SecondViewController: UIViewController {
// making this a weak variable so that it won't create a strong reference cycle
weak var delegate: DataEnteredDelegate? = nil
#IBOutlet weak var textField: UITextField!
#IBAction func sendTextBackButton(sender: UIButton) {
// call this method on whichever class implements our delegate protocol
delegate?.userDidEnterInformation(textField.text!)
// go back to the previous view controller
self.navigationController?.popViewControllerAnimated(true)
}
}
Note that the protocol is outside of the View Controller class.
That's it. Running the app now you should be able to send data back from the second view controller to the first.
Original post: https://stackoverflow.com/a/33229483/13783496
There are 2 methods:
You can create a segue from button in endviewcontroller to firstviewcontroller in storyboard. You can configure the func prepare(for segue: UIStoryboardSegue, sender: Any?) for it.
let endVC = endViewcontroller()
endVC.color = "blue"
You can keep the variable whose value needs to be changed as static datatype.
On the click action of button, you can access variable as,
EndViewController.color = "Red".
Kindly use static variables only if you want other Viewcontrollers to access it directly.
Related
I've set up a simple Swift project to try and wrap my head around delegates & protocols. The goal is to pass data between two classes (SendingClass & ReceivingClass). Two buttons in the SendingClass are linked to the delegate which should trigger the Protocol conforming function in the ReceivingClass to execute. This doesn't work unfortunately, I suspect it has to do with where and how I am declaring the ReceivingClass as the delegate.
Appreciate your insights, i'm just starting out!
I've tried setting the delegate in various locations (presently within viewDidLoad, but cant get it to work).
let vc = SendingClass()
vc.statusDelegate = self
SendingClass.swift
import UIKit
protocol StatusDelegate {
func statusChanged(state: Bool, sender: String)
}
class SendingClass: UIViewController {
var statusDelegate : StatusDelegate?
#IBAction func button1Pressed(_ sender: UIButton) {
statusDelegate?.statusChanged(state: true, sender: "Button 1")
}
#IBAction func button2Pressed(_ sender: UIButton) {
statusDelegate?.statusChanged(state: false, sender: "Button 2")
}
}
ReceivingClass.swift
import Foundation
import UIKit
class ReceivingClass: UIViewController, StatusDelegate {
override func viewDidLoad() {
let vc = SendingClass()
vc.statusDelegate = self
}
func statusChanged(state: Bool, sender: String) {
print("Sender = \(sender) , State = \(state)")
}
}
Expected: the ReceivingClass protocol conforming function (func statusChanged) should execute each time the buttons are pressed within the SendingClass.
Actual: Nothing happens
I am using this..
// create extension in your receiving class
extension ReceivingClass: PopUpVCDelegate {
func statusChanged(state: Bool, sender: String) {
print("Sender = \(sender) , State = \(state)")
}
}
// on sending class, when you present your receiving class on any button click
eg.
let resultController = self.storyboard?.instantiateViewController(withIdentifier: "PopUpVCID") as? PopUpVC
resultController?.delegate = self
self.present(resultController!, animated: true, completion: nil)
//or if not have button add on viewdidload in receiving class
// here is full eg
How to get data from popup view controller to custom table view cell?
For protocol and delegate, you use it when u want to bring a value from 2nd VC (presented by 1st or pushed by 1st VC) to 1st VC, which is the original.
From your code, I dont see you presenting or pushing your 2nd VC. that's why it's not working. Hopefully I answered your doubt.
However if you still want to bring a value over from 1st VC to 2nd VC. In second VC, create a variable to receive it
var ReceivedData = String()
then from your first VC, when u are going to push it,
let vc = SendingClass()
vc.ReceivedData = "Whatever you want it to receive"
If you're using storyboard segues, maybe the view controller is instantiated from there so probably you have to use the prepareForSegue and get the destination view controller (which is already instantiated for you) in the ReceivingClass view controller:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let destination = segue.destination as? SendingClass {
destination.delegate = self
}
}
Also be careful with delegate patter: the delegate property should be declared as a weak property to avoid retain-cycle
weak var delegate: MyDelegate?
Trying to pass data from one view controller MainScreenVC to Another RatesVC with protocol and extension, but that's not working, app crashing everytime . I'm clearly see that problem with code on second VC(because print showing correct data after action on first VC) but not sure where is error.
StoryBoard and 1st VC Example
Second VC
1st View controller
import UIKit
protocol transferNameOfCurrency {
func currencySelected(nameOfCurrency: String)
}
class MainScreenVC: UIViewController {
var transferCurrencyDelegate: transferNameOfCurrency?
var nameOfTheCurrency: String?
#IBAction func updateRates(_ sender: Any) {
nameOfTheCurrency = "EUR"
transferCurrencyDelegate?.currencySelected(nameOfCurrency:
nameOfTheCurrency)
print(nameOfTheCurrency)
}
}
2nd ViewController
import UIKit
class RatesVC: UIViewController {
var currencySelected: String?
override func viewDidLoad() {
super.viewDidLoad()
if let push = self.storyboard?.instantiateViewController(withIdentifier: "MainScreenVC") as? MainScreenVC
{
push.transferCurrencyDelegate = self
}
// Do any additional setup after loading the view.
}
}
extension RatesVC: transferNameOfCurrency {
func currencySelected(nameOfCurrency: String) {
currencySelected = nameOfCurrency
print(currencySelected)
}
}
The most obvious problem lies here:
if let push = self.storyboard?.instantiateViewController(withIdentifier: "MainScreenVC") as? MainScreenVC {
push.transferCurrencyDelegate = self
}
You have to realize that instantiateViewController creates a new view controller - it's not the reference to the view controller presented at the screen. In that code you just created a completely new view controller and then set its delegate to self, but otherwise nothing else.
Without knowing the context it is really hard to suggest anything - prepare(for:) segue might be the place where you want to set the delegate. Anyway, the problem is that you have to obtain a reference to the controller that is presented on the screen, the one that is supposed to be reacting to those events.
Moreover, from the memory management aspect, you should really consider making the delegate property a weak one to prevent memory leaks.
EDIT
So after seeing the minimal working example you provided at link, I think I can provide the solution on how to get that string to the SecondVC.
Your first view controller with comments:
import UIKit
class ViewController: UIViewController {
var newLine: String = "EUR"
#IBAction func push(_ sender: Any) {
// here the secondVC does not exist yet, calling delegate.transferWord() here would have no sense
// performSegue will create that secondVC, but now it does not exist, nor it is set up as the delegate
self.performSegue(withIdentifier: "ViewController", sender: navigationController)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let secondVC = segue.destination as? SecondVC, segue.identifier == "ViewController" {
// at this moment secondVC did not load its view yet, trying to access it would cause crash
// because transferWord tries to set label.text directly, we need to make sure that label
// is already set (for experiment you can try comment out next line)
secondVC.loadViewIfNeeded()
// but here secondVC exist, so lets call transferWord on it
secondVC.transferWord(word: newLine)
}
}
}
No need for delegates here, because your ViewController is the one pushing the SecondVC to the Navigation controller - that means that you can access it directly in prepare(for:), as you can see above.
Now the SecondVC is super simple (I omitted unnecessary code):
import UIKit
class SecondVC: UIViewController {
#IBOutlet weak var label: UILabel!
func transferWord(word: String) {
label.text = word
}
}
Storyboards can stay as they are.
does anyone know how to pass an action from ViewController to its ContainerViewController. I try to hide the container view by an action that is triggered by itself. The ContainerViewController is embedded in the container view.
ViewController:
#IBOutlet weak var ChoseLanguageContainer: UIView!
**ContainerViewController:**
#IBAction func action(_ sender: Any) {
ViewController().containerView.isHidden = true
} //I know this does not work
I had the similar requirement, I created my own delegate methods which were implemented in ContainerViewController.
protocol ContainerViewControllerDelegate :class{
func notifyItemChange(any params you need to pass.)
}
In my container ViewController, I created a variable for delegate.
var changeContainerDelegate :ContainerViewControllerDelegate?
In my parent View Controller which contains the container, I did the following.
To get the instance of view controller which is embedded in the container.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let containerViewController = segue.destination as? ContainerViewController{
containerViewController.changeContainerDelegate = self
}
Create IBOutlet of container view in your view controller.
Conform to this protocol and write the implementation in View controller.
func changeContainerVC(containerVCName : String ,dataToBePassed:[AnyObject]?) {
containerView.isHidden = true
}
In the button action inside the ContainerViewController call the delegate like :
changeContainerDelegate?.notifyItemChange()
This works for me. Hope helps ou too!
In your code:
ViewController().containerView.isHidden = true
You are making a new instance of the ViewController and using it to hide the containerView. This won't work. Instead you need to hide the containerView of the current instance of ViewController, i.e self.
Here is the code you can try:
class ViewController: UIViewController
{
#IBOutlet weak var containerView: UIView!
override func viewDidLoad()
{
super.viewDidLoad()
}
#IBAction func hideContainerView(_ sender: UIButton)
{
self.containerView.isHidden = true
}
}
View Hierarchy:
Let me know if you face any other issue regarding this.
I have a parent UIViewController and it has two different view containers - each of them has embedded UIViewController inside. It looks somehow like this:
I want to change the label on the right container when user presses the button stored on the left one.
So far I was able to do it while having a button placed in a parent view controller, then I was just using a protocol:
in my parent component I had:
class ParentController: UIViewController {
var delegateEmbedded:HandleEmbedded?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "segueToFirstEmbeddedController"){
if let embeddedView = segue.destinationViewController as? EmbeddedContainer {
self.delegateEmbedded = embeddedView
}
}
in my container-embedded UIViewController I had:
protocol HandleEmbedded: class {
func setName(label: String)
}
class EmbeddedContainer: UITableViewController, HandleYourChat{
func setName(label: String){
print("setting label to \(label)")
}
}
Situation above works when I have the button placed in a parent controller and want to change the label inside a container. But what happens and how should I pass the data when the button is also embedded, but in a different container than the label?
Do I have to pass the data through the parent controller? What's the best way for doing so?
To pass data from one embedded ViewController to another embedded ViewController, have the parent handle the transfer. Here I have provided a complete example with three ViewControllers and a single StringTaker protocol. Both the main ViewController and the LabelViewController implement this protocol. The main ViewController takes a string from the ButtonViewController and passes it on to the embedded LabelViewController.
ViewController.swift
import UIKit
protocol StringTaker: class {
func takeString(string: String)
}
class ViewController: UIViewController, StringTaker {
weak var stringTaker: StringTaker?
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EmbedButtonViewController" {
let dvc = segue.destinationViewController as! ButtonViewController
dvc.delegate = self
} else if segue.identifier == "EmbedLabelViewController" {
let dvc = segue.destinationViewController as! LabelViewController
stringTaker = dvc
}
}
// Receive the string from the ButtonViewController
func takeString(string: String) {
// Pass it to the LabelViewController
stringTaker?.takeString(string)
}
}
ButtonViewController.swift
import UIKit
class ButtonViewController: UIViewController {
weak var delegate: StringTaker?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func generateString(sender: UIButton) {
let cities = ["Boston", "Paris", "Sydney", "Mumbai", "Lima"]
// Pick a random city
let city = cities[Int(arc4random_uniform(UInt32(cities.count)))]
// Pass the string to the delegate
delegate?.takeString(city)
}
}
LabelViewController.swift
import UIKit
class LabelViewController: UIViewController, StringTaker {
#IBOutlet weak var myLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func takeString(string: String) {
myLabel.text = string
}
}
Things to note:
The LabelViewController and the ButtonViewController know nothing about the ViewController that uses them. This makes it easier to reuse them. You could embed them in another viewController and as long as you properly implement the StringTaker protocol and set up the delegate, everything works.
The key to hooking this up in in naming the embed segues and then properly setting up the delegates in prepareForSegue. The segues can be found in the Document Outline view once the Container is added to the ViewController.
This question already has answers here:
How do you share data between view controllers and other objects in Swift?
(9 answers)
Closed 6 years ago.
For example I've got 2 controllers that already in memory (Class 1, Class 2). How can I access Class 1 data from Class 2?
class Class_1: UIViewController {
var number:UInt8 = 1
override func viewDidAppear(animated: Bool) {
number = 8
}
}
How do I access number variable in Class 2 and print it value? The point is to not make new instance, the point is to get pointer for Class 1 in memory and get access to it's data.
First of all change the name of your ViewController from Class_1 to Class1ViewController and Class_2 to Class2ViewController
You need to set the variable of Class2ViewController while intializing it in Class1ViewController, and to pass the data back from Class2ViewController to Class1ViewController you need to use delegate
For data transfer Class1ViewController to Class2ViewController, Open your Class1ViewController file and add the following prepareForSegue method if you are using storyboard
class Class1ViewController: UIViewController{
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Class2ViewController" {
if let class2ViewController = segue.destinationViewController as? Class2ViewController {
class2ViewController.inforFromClass1 = "Class 2 Variable set from Class 1"
}
}
}
}
However, If you are using xib and have a button for moving to Class2ViewController from Class1ViewController, following code should be written in IBAction of button that triggers Class2ViewController
class Class1ViewController: UIViewController{
#IBAction showClass2ViewController(){
let secondViewController = SecondViewController(nibName:"SecondViewController", bundle: NSBundle.mainBundle())
secondViewController.infoFromClass1 = "Class 2 Variable set from Class 1"
self.showViewController(secondViewController, sender: self)
}
}
This is how you set the variable on Class2ViewController while showing it from Class1ViewController
Now to pass message from Class2ViewController to Class1ViewController you need to use delegates. Open you Class2ViewController and add the following protocol at the top
#objc protocol Class2ViewContollerDelegate :class{
func printMessageFromClass2ViewController()
}
class Class2ViewController: UIViewController {
}
Add a weak reference to the delagate in Class2ViewController class and call it in its ViewDidAppear or any other method you like,
#objc protocol Class2ViewContollerDelegate :class{
func printMessageFromClass2ViewController()
}
class Class2ViewController: UIViewController {
weak var delegate: Class2ViewControllerDelegate?
override func viewDidAppear(animated: Bool) {
self.delegate?.printValueFromClass2ViewController
}
}
Now that we have define the protocol in Class2ViewController we need to implement it in Class1ViewController. Back In your Class1ViewController file implement the protocol like this
class Class1ViewController: UIViewController, Class2ViewControllerDelegate {
func printMessageFromClass2ViewController(){
print("hey I just printed message in Class1ViewController through its delegate in Class2ViewController")
}
// For Xibs
#IBAction showClass2ViewController(){
let secondViewController = SecondViewController(nibName: "SecondViewController", bundle: NSBundle.mainBundle())
secondViewController.infoFromClass1 = "Class 2 Variable set from Class 1"
self.showViewController(secondViewController, sender: self)
}
// For storyboards
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Class2ViewController" {
if let class2ViewController = segue.destinationViewController as?Class2ViewController {
class2ViewController.inforFromClass1 = "Class 2 Variable set from Class 1"
}
}
}
}