I've got two View Controllers. Main and Temporary one. The second one performs an action on the different screen (is called by pushViewController) and then I'm popping (popViewController) and would like to present the returned value which is String.
I've tried using protocol but it's nil.
Here is my code:
SecondVC.swift:
protocol ValueDelegate {
func append(_ text: String)
}
class SecondViewController: UIViewController{
var delegate: ValueDelegate!
...
...
private func function(){
if let delegate = self.delegate{
delegate.append(value.stringValue)
}
navigateBack()
}
private func navigateBack(){
if let navigation = self.navigationController{
navigation.popViewController(aniamted: true)
}
}
MainVC.swift:
class MainViewController: UIViewController, ValueDelegate {
var secondVC = SecondViewController()
...
func append(_ value: String) {
textField.text?.append(barcode)
}
...
override func viewDidLoad(){
super.viewDidLoad()
self.secondVC.delegate = self
}
}
Use these links to understand exactly how to use Protocols in swift:
Passing data between two ViewControllers (delegate) - Swift
Passing Data between View Controllers
You have to implement below line of code in first view controller :-
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSecondViewController" {
let secondViewController = segue.destination as! SecondViewController
secondViewController.delegate = self
}
}
I've tried using protocol but it's nil.
Because you never set it to anything. It was your job, when you pushed the SecondViewController, to set its valueDelegate to the MainViewController. But you didn't.
What you did do was set the valueDelegate of another SecondViewController to the MainViewController:
var secondVC = SecondViewController()
self.secondVC.delegate = self
That was silly, because secondVC is a different, newly made instance of SecondViewController having nothing at all to do with your real interface. In particular, it is not the SecondViewController instance that gets pushed. But that is the instance you need to set the delegate of.
Related
I'm getting data from second VC to first VC using protocol or delegates, Data is receiving in first VC but the problem is that Data is not showing in Textfield. Here is my Complete Code for understanding. Any Effort is appreciated.
FirstVC class
import UIKit
class firstViewController: UIViewController, UITextFieldDelegate, MyProtocol {
var valueSentFromSecondViewController : String?
#IBOutlet weak var myTextField : UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func myTextFieldACTIONWhenEditingDidBegin(_ sender: Any) {
myTextField.isUserInteractionEnabled = false
let secondVC = self.storyboard?.instantiateViewController(withIdentifier: "secondViewController") as! secondViewController
secondVC.delegate = self
self.navigationController?.pushViewController(secondVC, animated: true)
}
func setResultsAfterEvaluation(valueSent: String) {
self.valueSentFromSecondViewController = valueSent
print(valueSentFromSecondViewController!) // Ahtazaz(DATA showing here)
myTextField.text = valueSentFromSecondViewController //This's the problem, Why not showing here in this this TextField
}
}
Now, SecondVC Class
import UIKit
protocol MyProtocol {
func setResultsAfterEvaluation(valueSent: String)
}
class secondViewController: UIViewController {
var delegate : MyProtocol?
var sentValue : String?
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func btn(_ sender: Any) {
let firstVC = self.storyboard?.instantiateViewController(withIdentifier: "firstViewController") as! firstViewController
self.navigationController?.pushViewController(firstVC, animated: true)
sentValue = "Ahtazaz"
delegate?.setResultsAfterEvaluation(valueSent: sentValue!)
}
}
You are pushing SecondVC. Then in SecondVC you are pushing again FirstVC.
I think this is where you are making mistake.
let firstVC = self.storyboard?.instantiateViewController(withIdentifier: "firstViewController") as! firstViewController
You are creating a new instance of FirstVC. Then you push it which is wrong. Call your delegate and then Pop back to previous(FirstVC) controller
Try this code in your button action
#IBAction func btn(_ sender: Any) {
sentValue = "Ahtazaz"
delegate?.setResultsAfterEvaluation(valueSent: sentValue!)
self.navigationController?.popViewController(animated: true)
}
This should be the correct approach rather than pushing the controller again.
The steps are Simple to use Delegates for passing the data to previous VC
Second VC:
At the top of VC declare the protocol as follows:
protocol MenuListingDelegate {
func callBackOfMenuSelected(arrSelectedCategory:[Int],isFromWhichPopup:Int)
}
Then inside that define the variable like this
var delegate:MenuListingDelegate?
And then provide the data to the delegate like this. In my case i provide that on click of button before pop View Controller
self.delegate?.callBackOfMenuSelected(strToPass: "Hello")
Now in First VC:
At the top define the Delegate method like this:
class DayDetailVC: UIViewController,MenuListingDelegate {}
And fetch the Data like this
//MARK:- Menu Listing Delegate
func callBackOfMenuSelected(strToPass: String) {
print(strToPass)
}
Note:- Do not forget to declare the delegate of the secondVC where we use this. secondVC.delegate = self.
Edit Check the following cases
Case 1:- Check the outlets of the myTextField i guess the issue is there. If everything is correct remove the Outlet and the set that again
Case 2:- Still if doesnt work then try setting like this
func setResultsAfterEvaluation(valueSent: String) {
myTextField.text = "\(valueSent)"
}
Hope this helps.
Edit 2
I have seen you have used pushViewController in the following lines:
So you can simply use the following line of code to pass the data to firstVC
In SecondVC add following Code:
let firstVC = self.storyboard?.instantiateViewController(withIdentifier: "firstViewController") as! firstViewController
firstVC.valueSentFromSecondViewController = "Hello World"
self.navigationController?.pushViewController(firstVC, animated: true)
Now in FirstVC
Use like in viewDidLoad() or anywhere you want
print(valueSentFromSecondViewController) //Hello World
Cheers it Done.
Choose the way you want.
Note:- But i will suggest you to use popViewController instead of
pushViewController when returning back from SecondVC -> FirstVC. Rest depends upon your requirements.
Hope this helps.
I have searched through the net for couple of hours now thought this would be an easy task, but things are not going as planned. This question is edited, some of great fella has given great answer but I am still struggling.
Here is the scenario, I have a viewControllerA which is always visible. There is a small button on top of this view, when I click it there comes viewControllerB as a static tableViewController. Its a slide view from left to right to be honest like other apps.
There is one section and couple of rows in the tableView, when I tap 4th row I present viewControllerC, there is a UISwitch button there. When I dismiss the viewControllerC, viewControllerA appear again. viewControllerB is my menu controller therefore its not my greater concern .Now I want to pass data from viewControllerC to viewControllerA. Here is my broken code:
for viewControllerC :
class viewControllerC: UIViewController {
..//
#IBAction func switchTapped(_ sender: UISwitch) {
let vc = viewControllerA()
if sender.isOn == true {
vc.state = true
} else if sender.isOn == false {
vc.state = false
}
}
..//
}
for viewControllerA :
class viewControllerA: UIViewController, GMSMapViewDelegate {
var state:Bool?
...//
if self.state == true {
self.mapView.isTrafficEnabled = true
} else {
self.mapView.isTrafficEnabled = false
}
}
But its not working an I know I am not heading to the right direction. As you can see from the example I want to send ture when UISwitch is on and false when its off from viewControllerC to viewControllerA. Some of the folks have suggested delegate method but I am still struggling. I was following this link , I think "Passing data backwards through the shared state of the app" section meets my criteria. Although I need help. Thanks in advance.
You can pass data with Delegate pattern, here's an idea:
import UIKit
// Make a delegate protocol
protocol ViewControllerBDelegate: class {
func didTapSwitch(isOn: Bool)
}
class ViewControllerB: UIViewController {
// Make a weak delegate reference in VC B
weak var delegate: ViewControllerBDelegate?
var state: Bool?
// On action trigger delegate method:
#IBAction func switchTapped(_ sender: UISwitch) {
delegate?.didTapSwitch(isOn: sender.isOn)
}
}
class ViewControllerA: UIViewController {
var stateFromSwitch: Bool?
// In this VC you are instantiating viewController B
// ... code ...
// set delegate: viewControllerB.delegate = self
}
// implement ViewControllerBDelegate
extension ViewControllerA: ViewControllerBDelegate {
func didTapSwitch(isOn: Bool) {
stateFromSwitch = isOn
}
}
First of all when you initiate viewControllerA :
let vc = viewControllerA()
You are creating new instance of viewcontrol which doesn’t reference to your first view control.
You can pass data to viewcontrollers in different ways.
You can use delage pattern or you can use unwind.
In delegate method first you define a class type protocol with a function definition for changing something in viewControllerA.
protocol ViewControllerBDelegate: class {
func changeSwitch(toValue: Boolean)
}
Then in ViewControllerB you define a weak reference to delegate
weak var delegate: ViewControllerBDelegate?
Then you adopt this protocol on ViewControlA:
extension ViewControllerA: ViewControllerBDelegate {
func changeSwitch(toValue: Boolean) {
state = toValue
}
}
When you want to present or push to ViewControllerB you should set this variable to self
let vc = ViewControllerB()
vc.delegate = self
present(vc, animated: true, completion: nil)
// or navigationController. pushViewController(vc, animated: true)
if you are using segue to navigate from one viewcontroller to another, you should set delegate variable in prepare(for segue, sender). Override this function in ViewControllerA
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mySegue" ,
let vc = segue.destination as? ViewControllerB {
vc.delegate = self
}
}
Then when switch value changed you can use delegate to change value in ViewControllerA
delegate?.changeSwitch(toValue: sender.isOn)
on wind let you pop or dismiss child viewcontrollers to a certain parent and then do something. You can read a full tutorial here
EDIT
for chain delegates you can pass a delegate to ViewController B, then pass the same delegate to ViewController C.
in view controller C you define the same type delegate
weak var delegate: ViewControllerBDelegate?
then in view controller B when you are navigating to view controller c you pass the same delegate
let vc = ViewControllerC()
vc.delegate = self.delegate
present(vc, animated: true, completion: nil)
EDIT 2
SWRevealViewController is different scenario. revealController have an property named frontViewController. which can be your ViewControllerA if you dont push any other controllers on reveal. handling it with frontViewController is tricky you should be sure if frontController is ViewControllerA.
so i suggest you use another method to communicate with ViewControllerA. you can use NotificationCenter.
extension Notification.Name {
static let updateMap = Notification.Name("updateMap")
}
in ViewControllerA
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(updateMap(_:)), name: .updateMap, object: nil )
}
#objc func updateMap(notification: NSNotification) {
if let state = notification.userInfo?["state"] as? Bool {
// do something with state
}
}
and in ViewControllerC you post a notification when switch value is Changed:
let userInfoDic:[String: Bool] = ["state": sender.isOn]
// post a notification
NotificationCenter.default.post(name: .updateMap, object: nil, userInfo: userInfoDic)
if frontViewController in reveal is pushed again. reveal will initiate
new ViewControllerA for frontViewController. in this scenario you have
to set settings in UserDefault and in ViewControllerA read this
settings.
using UserDefaults :
in ViewControllerC
UserDefaults.standard.setValue(sender.isOn, forKey: "mapState")
in ViewControllerA
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let state = UserDefaults.standard.value(forKey: "mapState") ?? false
self.mapView.isTrafficEnabled = state
}
I know the same question is asked many times. I read most of the answers from stack overflow and tried. But it did not help my problem.
I have two view controllers
protocol UpdateDataDelegate {
func loadData()
}
viewcontroller2 {
var delegate: UpdateDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
fun saveData() {
self.delegate?.loadData()
}
}
viewcontroller1 : UpdateDataDelegate {
var vc2 = viewcontroller2()
override func viewDidLoad() {
super.viewDidLoad()
vc2.delegate = self
}
func loadData() {
}
}
But function loadData() from viewcontroller1 is not called.
Since I don't have the complete code before me I can only assume that the delegate is not assumed properly.
If the delegate is not initialised properly it cannot pass value to the other viewController.
You can check delegate is properly initialised by:
if let delegate = delegate{
//Do your works here
}else{
print("The delegate is nil")
}
if the delegate is nil is printed in console, then the problem might be in the way the delegate was initialised
This might be because you are setting the delegate and opening an another instance of the viewController which was not assigned the delegate value.
In the code you provided I see that you are setting the delegate as
var vc2 = viewcontroller2()
vc2.delegate = self
But I cannot see the code that you used to move to the viewController2. Now we have to present this assigned viewController. Instead of using segue to move to the viewcontroller2 present this vc using the code below
present(vc2, animated: true, completion: nil)
You should place this according to your code logic.(where your segue is triggered)
Situation 2:
If you are using segue to move to the viewController2 then the delegate should be assigned in the prepareforSegue method as below
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc2 = segue.destination as? ViewController2{
vc2.delegate = self
}
}
let me know how it goes.
A simple playground for what you are trying to do, even if I have not clear what you are trying to achieve:
import UIKit
protocol UpdateDataDelegate: class {
func loadData()
}
class ViewController2: UIViewController {
weak var delegate: UpdateDataDelegate?
func saveData() {
self.delegate?.loadData()
}
}
class ViewController1: UIViewController {
}
extension ViewController1: UpdateDataDelegate {
func loadData() {
print("loadData called")
}
}
let viewController1 = ViewController1()
let viewController2 = ViewController2()
viewController2.delegate = viewController1
viewController2.saveData()
Few notes:
classes should be upper case. So, ViewController1 instead viewcontroller1
delegates should be weak otherwise you create reference cycles
class should be used for UpdateDataDelegate protocol otherwise compiler will complain since weak cannot be applied to class and class-bound protocol types
prefer extension to conform to protocols. It makes the code easy to read
The only thing I see missing in your code is call to saveData() of ViewController2 that will in turn call loadData() of ViewController1.
So just add:
override func viewDidLoad()
{
super.viewDidLoad()
vc2.delegate = self
vc2.saveData() //Add this line to your code
}
You are good to go now :)
Edit:
protocol UpdateDataDelegate
{
func loadData()
}
class ViewController2: UIViewController
{
var delegate: UpdateDataDelegate?
func saveData()
{
self.delegate?.loadData()
}
}
class ViewController1: UIViewController, UpdateDataDelegate
{
var vc2 = ViewController2()
override func viewDidLoad()
{
super.viewDidLoad()
vc2.delegate = self
vc2.saveData()
}
func loadData()
{
print("Done")
}
}
I have used the above code and it is working fine for me. How are you executing it? I have used storyboard and used ViewController1 as the Initial View Controller.
I assume that you need to load data when your delegate has been set up. In this case you can use magic didSet:
weak var delegate: UpdateDataDelegate? {
didSet {
self.saveData()
}
}
So right after setting the delegate the needed method will be called.
I have three view controllers like below
I wrote the unwind method in viewcontroller1, and try to receive some data from viewcontroller2 and viewcontroller3 when they unwind to viewcontroller1.
#IBAction func unwindToViewController1(segue: UIStoryboardSegue) {
print("1")
main_content = (segue.source as! MainContentViewController).main_content
}
#IBAction func unwindToViewController2(segue: UIStoryboardSegue) {
print("2")
detailed_content = (segue.source as! SupplementContentViewController).supplement
}
And set the exit unwind segue for both controller 2 and 3 already.
But why the unwindToViewController methods never get called correctly? I think they should be called when I click the button automatically created by the system.
I solve this problem by using delegate instead of unwind. Delegate pattenr is a more explicit way to solve this problem.
By creating a protocol called myDelegate
protocol myDelegate {
func updateMaincontent(main_content : String)
func updateSupplement(supplement: String)
}
And create a delegate instance inside the second view controller
var delegate: myDelegate?
Then in the first view controller, make the class extend myDelegate and set this delegate of second view controller to self in the func prepare(for segue: UIStoryboardSegue, sender: Any?) method
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationViewController = segue.destination as? MainContentViewController {
destinationViewController.main_content = self.main_content
destinationViewController.delegate = self
}
else if let destinationViewController = segue.destination as? SupplementContentViewController {
destinationViewController.supplement = self.detailed_content
destinationViewController.delegate = self
}
}
Finally, go back to second view controller, and set the value of delegate to what you want in viewWillDisappear method.
func viewWillDisappear(_ animated: Bool) {
self.main_content = contentTextView.text
delegate?.updateMaincontent(main_content: contentTextView.text)
}
I have two view controllers and two views.
In my first view, I set the variable 'currentUser' to false.
I need to be able to set 'currentUser' to true in the second view controller.
When trying to reference 'currentUser' from the second view it's not picking it up as 'currentUser' is defined in the first view controller.
How do I carry across variables with segue?
Set values from Any ViewController to a Second One using segues
Like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "yourIdentifierInStoryboard") {
let yourNextViewController = (segue.destinationViewController as yourNextViewControllerClass)
yourNextViewController.value = yourValue
And in your yourNextViewController class.
class yourNextViewControllerClass {
var value:Int! // or whatever
You can call this also programmatically:
self.performSegueWithIdentifier("yourIdentifierInStoryboard", sender: self)
Set values from your DestinationViewController back to your Primary (First) ViewController
1. Implement a protocol, for example create a file called protocol.swift.
protocol changeUserValueDelegate {
func changeUser(toValue:Bool)
}
2. set the delegate on your second View
class yourNextViewControllerClass {
var delegate:changeUserValueDelegate?
3. set the delegate on load (prepareForSegue)
if(segue.identifier == "yourIdentifierInStoryboard") {
var yourNextViewController = (segue.destinationViewController as yourNextViewControllerClass)
yourNextViewController.delegate = self
4. add Function to FirstViewController
func changeUser(toValue:Bool) {
self.currentUserValue = toValue
}
5. call this function from your SecondViewController
delegate?.changeUser(true)
6. Set the delegate in your FirstViewController
class FirstViewController: UIViewController, ChangeUserValueDelegate {
The problem here is that your currentUser variable is of type Bool, which is a value type. So passing it from your first view controller to your second view controller will in fact create a new Bool instance. What you need is to pass a reference from your first view controller to your second view controller (see Value and Reference Types for more details on value and reference with Swift).
Thereby, according to your needs/preferences, you may choose one of the three following examples.
1. The boxing style
Here, we "box" our Bool inside a class and pass a reference of that class instance to the second view controller.
1.1. Create a CurrentUser class:
class CurrentUser {
var someBooleanValue = true {
didSet {
print(someBooleanValue)
}
}
}
1.2. Create a UIViewController subclass for the first view controller:
import UIKit
class ViewController1: UIViewController {
let currentUser = CurrentUser()
override func viewDidLoad() {
super.viewDidLoad()
currentUser.someBooleanValue = false
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let viewController2 = segue.destinationViewController as? ViewController2 {
viewController2.currentUser = currentUser
}
}
}
1.3. Create a UIViewController subclass for the second view controller:
import UIKit
class ViewController2: UIViewController {
var currentUser: CurrentUser?
// Link this IBAction to a UIButton or a UIBarButtonItem in the Storyboard
#IBAction func toggleBoolean(sender: AnyObject) {
if let currentUser = currentUser {
currentUser.someBooleanValue = !currentUser.someBooleanValue
}
}
}
2. The closure style
Here, we get a weak reference of our first view controller in a closure and pass this closure to the second view controller.
2.1. Create a UIViewController subclass for the first view controller:
import UIKit
class ViewController1: UIViewController {
var currentUser = true {
didSet {
print(currentUser)
}
}
override func viewDidLoad() {
super.viewDidLoad()
currentUser = false
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let viewController2 = segue.destinationViewController as? ViewController2 {
let closureToPerform = { [weak self] in
if let strongSelf = self {
strongSelf.currentUser = !strongSelf.currentUser
}
}
viewController2.closureToPerform = closureToPerform
}
}
}
2.2. Create a UIViewController subclass for the second view controller:
import UIKit
class ViewController2: UIViewController {
var closureToPerform: (() -> Void)?
// Link this IBAction to a UIButton or a UIBarButtonItem in the Storyboard
#IBAction func toggleBoolean(sender: AnyObject) {
closureToPerform?()
}
}
3. The protocol-delegate style
Here, we make our first view controller conform to some protocol and pass a weak reference of it to the second view controller.
3.1. Create a custom protocol:
protocol MyDelegate: class {
func changeValue()
}
3.2. Create a UIViewController subclass for the first view controller and make it conform to the previous protocol:
import UIKit
class ViewController1: UIViewController, MyDelegate {
var currentUser = true {
didSet {
print(currentUser)
}
}
override func viewDidLoad() {
super.viewDidLoad()
currentUser = false
}
func changeValue() {
currentUser = !currentUser
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let viewController2 = segue.destinationViewController as? ViewController2 {
viewController2.delegate = self
}
}
}
3.3. Create a UIViewController subclass for the second view controller:
import UIKit
class ViewController2: UIViewController {
weak var delegate: MyDelegate?
// Link this IBAction to a UIButton or a UIBarButtonItem in the Storyboard
#IBAction func toggleBoolean(sender: AnyObject) {
delegate?.changeValue()
}
}
Add an attribute currentUserSecondVC in the destination view controller, and use prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Name Of Your Segue" {
var vc = segue.destinationViewController as NameOfTheSecondViewController
vc.currentUserSecondVC = !currentUser //you can do whatever you want with it in the 2nd VC
}
}
The function that should be defined as override is:
open func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "Segue Name Defined In Storyboard") {
//set the property of the designated view controller with the value you need
}
}
Since you're using same variable across the two Viewcontrollers, namely currentUser (type Bool).
So its better to make it a global variable in both classes.
When coming to global variable concept in swift.
Everything by default in swift is public, and thus if you declare something like this:
class FirstViewController: UIViewController {
var someVariable: Boll = YES
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
}
You can access it and set values as long as you have an instance of it:
var MySecondViewController: FirstViewController = FirstViewController(nibName: nil, bundle: nil)
var getThatValue = MySecondViewController.someVariable