How to pass data using delegates and protocols - ios

I have this first viewController which has UILabel in it and secondViewController which has UItextField and Add button. They are embedded in tabbar. I want to pass data from text field when add button is clicked to uilabel of first view controller.
protocol SendDelagate
{
func setData(string:String)
}
First View Controller is
class ViewController: UIViewController,SendDelagate{
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let vc = SecondViewController()
vc.delegates = self
}
func setData(string: String) {
label.text = string
}
}
And second ViewController is
class SecondViewController: UIViewController {
var delegates:SendDelagate?
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addButton(_ sender: UIButton) {
let text = textField.text!
delegates?.setData(string: text)
}
}

SecondViewController is embedded in UITabbarController?
You can find SecondViewController instance via TabbarController
class ViewController: UIViewController, SendDelagate {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if let secondViewController = tabBarController?.viewControllers?.first(where: { $0 is SecondViewController }) as? SecondViewController {
secondViewController.delegates = self
}
}
func setData(string: String) {
label.text = string
}
}

You can use User Defaults. So you save with your Add Button into User Defaults, and on viewDidLoad on the other view, you can load it.

Related

Why do I have to call my protocol functions two times to make it work?

I'm currently building a simple app to learn the protocol - delegate functionality. However my code doesn't work, even after I edited the way I did in my first practice app where it worked. In the FirstViewController there is a UILabel and a UIButton. When the user taps on the UIButton, a segue brings them to the SecondViewController where they should enter their name into a UITextField. After that, they can press the UIButton in the SecondViewController and the entered Name should be displayed in the UILabel in the FirstViewController.
I can't figure out why I have to call the protocol function two times in the #IBAction backButtonPresses(). Also, I can't figure out how to handle the same protocol function in the FirstViewController.
Here's the code for the FirstViewController.swift file:
import UIKit
class FirstViewController: UIViewController, SecondViewControllerDelegate {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var button: UIButton!
var labelText = ""
let secondVC = SecondViewController()
func changeLabelText(name: String) {
labelText = secondVC.enteredName
}
#IBAction func buttonPressed(_ sender: UIButton) {
performSegue(withIdentifier: "goToSecondVC", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as? SecondViewController
destinationVC?.delegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = secondVC.enteredName
}
}
And here's the code of the secondViewController:
import UIKit
protocol SecondViewControllerDelegate {
func changeLabelText(name: String)
}
class SecondViewController: UIViewController, SecondViewControllerDelegate {
var delegate: SecondViewControllerDelegate?
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var backButton: UIButton!
var enteredName: String = ""
func changeLabelText(name: String) {
enteredName = name
}
#IBAction func backButtonPressed(_ sender: UIButton) {
changeLabelText(name: nameTextField.text ?? "")
delegate?.changeLabelText(name: nameTextField.text ?? "")
print("das ist der name \(enteredName)")
dismiss(animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
You do not call any delegate method twice in backButtonPressed. You are calling two completely different changeLabelText methods. One is in SecondViewController, the other is on the delegate.
The call to changeLabelText in the SecondViewController is quite unnecessary. In fact, the enteredName property in SecondViewController is unnecessary. Also, SecondViewController should not be implementing its own delegate.
The call to changeLabelText on delegate is all you need. You just need to update the implementation of changeLabelText in FirstViewController to update the label's text with the new value.
func changeLabelText(name: String) {
nameLabel.text = name
}
Do not reference the secondVC property in FirstViewController. In fact, remove that property completely. It's not needed and it's referencing a completely different instance of SecondViewController than the one you actually present via segue.
I also suggest renaming the delegate method from changeLabelText to something more general such as enteredText. SecondViewController can be used by any other view controller to obtain some text. It's not specific to changing a label.
Here's your two view controllers with suggested changes:
class FirstViewController: UIViewController, SecondViewControllerDelegate {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var button: UIButton!
func enteredText(name: String) {
nameLabel.text = name
}
#IBAction func buttonPressed(_ sender: UIButton) {
performSegue(withIdentifier: "goToSecondVC", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as? SecondViewController
destinationVC?.delegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
protocol SecondViewControllerDelegate {
func enteredText(name: String)
}
class SecondViewController: UIViewController {
var delegate: SecondViewControllerDelegate?
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var backButton: UIButton!
#IBAction func backButtonPressed(_ sender: UIButton) {
let name = nameTextField.text ?? ""
delegate?.changeLabelText(name: name)
print("das ist der name \(name)")
dismiss(animated: true)
}
}

Delegate data from one UIViewController to another one

I am completely new to Swift programming and tried to delegate a single String from one ViewController to another by clicking a send button. The problem is , that it does not work ...
I guess it would be easy for you to solve this and considering that it would be very helpful wether you explain me what I did wrong. :)
Thank you a lot
import UIKit
protocol protoTYdelegate {
func didSendMessage(message: String)
}
class New: UIViewController {
#IBOutlet weak var SendButton: UIButton!
var tydelegate: protoTYdelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func SendButtonAction(_ sender: Any) {
let nachricht = "It works fine."
tydelegate?.didSendMessage(message: nachricht)
}
}
import UIKit
class ThankYouPage: UIViewController {
#IBOutlet weak var numbersView: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let controller = New()
controller.tydelegate = self
}
}
extension ThankYouPage: protoTYdelegate{
func didSendMessage(message: String) {
numbersView.text = message
}
As far as I understand, this code block doesn't work but the problem is not in the code, it's actually way that you choose to send data. In iOS development, there are many ways to send data. In your case, you need to use prepareForSegue method to send data to new class, not necessary to use delegates.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "ThankYouPage") {
let vc = segue.destination as! ThankYouPage
vc.message = "Message that you want to send"
}
}
And you need to implement your ThankYouPage as:
class ThankYouPage: UIViewController {
#IBOutlet weak var numbersView: UILabel!
var message = ""
override func viewDidLoad() {
super.viewDidLoad()
numbersView.text = message
}
}
In addition to that, you can use didSet method to print out the message to label instead of printing it directly in viewDidLoad method. Simply:
class ThankYouPage: UIViewController {
#IBOutlet weak var numbersView: UILabel!
var message: String?{
didSet{
numbersView.text = message
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
I hope this helps you.
#Eyup Göymen's answer is right.
I have another way, assuming that you are not using segue and you are pushing to next controller by manual-code.
So your ThankYouPage code should be like :
import UIKit
class ThankYouPage: UIViewController {
#IBOutlet weak var numbersView: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func someButtonAction(_ sender: Any) { // By clicking on some, you are opening that `New` controller
let detailView = self.storyboard?.instantiateViewController(withIdentifier: "New") as! New
detailView.tydelegate = self
self.navigationController?.pushViewController(detailView, animated: true)
}
}
extension ThankYouPage: protoTYdelegate {
func didSendMessage(message: String) {
numbersView.text = message
}
}

Button Triggered Issues

I'm getting data back from secondVC to firstVC and showing it in a text field.
My VC's are FirstVC with a text field and secondVC with a button.
The flow of my VC is when a user clicks on the text field (here text field action Editing Did Begin called) in firstVC then the secondVC will open. And then when the button clicks in secondVC then goes back to firstVC with some data and data will be show in same text field.
So the above is all working fine.
Now I want that the second time when again I click on the text field (now text field contains some data) so then again go to secondVC.
The problem is that now the text field contains data. When I click on it, it doesn't work because of button action property Editing Did Begin.
How to handle this?
Below is my code,
First VC
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()
}
#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!)
myTextField.text = valueSent
//print(valueSent)
}
}
Second VC
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) {
sentValue = "Ahtazaz"
delegate?.setResultsAfterEvaluation(valueSent: sentValue!)
self.navigationController?.popViewController(animated: true)
}
}
I believe this is a more simple way to do what you are trying to achieve, as it doesn't involve protocols and extra functions. If you have any questions, please ask. :)
First View Controller:
import UIKit
//By declaring a variable outside of any class, it is always active in memory and accessible anywhere.
var textFieldText: String = ""
class ViewController1: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField1: UITextField!
func textFieldDidBeginEditing(_ textField: UITextField) {
//textField.resignFirstResponder is used to dismiss the keyboard. By putting it in this function, it hides the keyboard, which prevents users from entering custom text into your text field.
textField.resignFirstResponder()
let VC2 = self.storyboard?.instantiateViewController(withIdentifier: "ViewController2") as! ViewController2
self.navigationController?.pushViewController(VC2, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
//This links textField1 on your storyboard to the textFieldDidBeginEditing function.
textField1.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//Every time the view is about to appear, this is called. This is where we update the text field's text.
textField1.text = textFieldText
}
}
Second View Controller:
import UIKit
class ViewController2: UIViewController {
#IBOutlet weak var button1: UIButton!
#IBOutlet weak var button2: UIButton!
//If the data you are trying to pass is the button's title, use these two functions.
#IBAction func button1Tapped(_ sender: UIButton) {
textFieldText = (button1.currentTitle)!
self.navigationController?.popViewController(animated: true)
}
#IBAction func button2Tapped(_ sender: UIButton) {
textFieldText = (button2.currentTitle)!
self.navigationController?.popViewController(animated: true)
}
//If the data you are trying to pass is not the button's title, use these.
#IBAction func button1Tapped(_ sender: UIButton) {
textFieldText = "Your Text Here"
self.navigationController?.popViewController(animated: true)
}
#IBAction func button2Tapped(_ sender: UIButton) {
textFieldText = "Your Text Here"
self.navigationController?.popViewController(animated: true)
}
}

Swift navigate back from view2 to view1

I have View1 in which button has title and I did successful segue to View2 with passing the Button title String which will be assigned to View 2 Button also.
Now when user go back from View2 to View1 , View1 Button has no value. How can I pass button value to View1?
I tried delegate method.. But no success :
Edited
View1
class PatientBreifInfoViewController: UIViewController, TasksViewDelegate {
var passName: String!
#IBOutlet weak var patientName: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
patientName.setTitle(passName, forState: .Normal)
}
func setName(name: String) {
patientName.setTitle(name, forState: .Normal)
print("View1")
print(name)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "toTasks") {
let controller = segue.destinationViewController as! TasksViewController
controller.delegate = self
controller.passName = passName
}
}
}
View2
protocol TasksViewDelegate: class {
func setName(patientName: String)
}
class TasksViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var passName : String!
#IBOutlet weak var backButton: UIButton!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var patientName: UIButton!
weak var delegate: TasksViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
patientName.setTitle(passName, forState: .Normal)
}
#IBAction func backButton(sender: AnyObject) {
delegate!.setName(passName)
self.navigationController?.popViewControllerAnimated(true)
}
}
Your protocol and delegate should work fine. However, you implementation of setName in ViewController1 is not using the Name parameter passed into it however:
func setName(Name: String)
{
passName = patientName
}
Should be changed to something like:
func setName(Name: String)
{
patientName = Name
}
You should also follow the swift naming convention of variables, parameters and functions starting with lowercase names and types beginning with uppercase names. So should be setName(name: String) not setName(Name: String) which would also mean changing patientName = Name to patientName = name in the example.
Update
Because your view controller will stay in memory while ViewController2 is on screen viewDidLoad will not get called again when transitioning from ViewController2 to ViewController1. Instead, you can set the button's title directly in your delegate method. For example:
func setName(name: String)
{
patientName.setTitle(name, forState: .Normal)
}
popViewController(animated:) removes/pops current view controller from navigation stack. Now you can use first view controller of navigation stack to access its members. You may not need to use delegate here
Try this and see:
class TasksViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBAction func backButton(sender: AnyObject) {
self.navigationController?.popViewControllerAnimated(true)
if let pbiViewController = self.navigationController?.viewControllers.first as? PatientBreifInfoViewController {
pbiViewController.passName = passName
}
}
}
class PatientBreifInfoViewController: UIViewController {
var passName: String?
}

Passing Data in Swift Between View Controllers in Same File

Why can't I pass data from one view controller class to another view controller class when they are in the same .swift file?
I've done both of the tutorials linked bellow in an effort to understand passing data, and both have you make another .swift file for you second view controller. When I followed them exactly it works. When I do everything the same but just put the class SecondViewController in the same .swift file as the class FirstViewController it doesn't work. Why?
I'm building an application with three view controllers and I'd rather just keep all the code in one file if I can. Is this why it's not working or is it just bad form?
Tutorials:
How to Pass Data from View Controller (Swift : Xcode) on YouTube
Segue Between Swift View Controllers on CodingExplorer
This works:
// ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet var TextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var DestViewController: ViewTwo = segue.destinationViewController as ViewTwo
DestViewController.LabelText = TextField.text
}
}
// ViewTwo.swift
import UIKit
class ViewTwo: UIViewController {
#IBOutlet var Label: UILabel!
var LabelText = String()
override func viewDidLoad() {
Label.text = LabelText
}
}
This does not:
// ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet var TextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var DestViewController: ViewTwo = segue.destinationViewController as ViewTwo
DestViewController.LabelText = TextField.text
}
}
class ViewTwo: UIViewController {
#IBOutlet var Label: UILabel!
var LabelText = String()
override func viewDidLoad() {
Label.text = LabelText
}
}
Working fine for me.
Have you set the classes for your views properly? The view with your textField (and probably a button to ViewTwo) should be set to ViewController and your second view with the label should be set to ViewTwo.
Check the connections between your label/textField. Are they properly connected to your class?
This is the code I used, which should be exactly the same:
import UIKit
class ViewController: UIViewController {
#IBOutlet var TextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var DestViewController = segue.destinationViewController as! ViewTwo
DestViewController.LabelText = TextField.text
}
}
class ViewTwo: UIViewController {
#IBOutlet var Label: UILabel!
var LabelText = String()
override func viewDidLoad() {
Label.text = LabelText
}
}

Resources