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
}
}
Related
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)
}
}
I'm just starting out with swift and decided to create a calorie counting app to test my skills in which I am using an Api to get the nutrition data.
Pressing the add breakfast/lunch/dinner segues to a search view controller from which I pass the calories back.
I am using protocol delegate design pattern. I wanted to know how I could set it up so that when I press the add breakfast button, only the breakfast calorie label is updated and when I press add lunch or dinner, their calorie labels are updated accordingly. any help would be greatly appreciated! I posted the codes of my logViewController and SearchViewController
import UIKit
protocol DataDelegate {
func updateLogCalories(str: String?)
}
class SearchViewController: UIViewController,UITextFieldDelegate,CalorieManagerDelegate{
var delagate: DataDelegate?
#IBOutlet weak var searchTF: UITextField!
#IBOutlet weak var calorieLabel: UILabel!
#IBOutlet weak var foodNameLabel: UILabel!
var calorieManager = CalorieManager()
var logCals : String?
override func viewDidLoad() {
super.viewDidLoad()
calorieManager.delegate=self
searchTF.delegate=self
}
#IBAction func searchPressed(_ sender: Any) {
searchTF.endEditing(true)
print(searchTF.text!)
}
#IBAction func addButtonPressed(_ sender: UIButton) {
delagate?.updateLogCalories(str: logCals)
self.dismiss(animated: true, completion: nil)
}
class LogViewController: UIViewController{
var breakfastCal: String?
#IBOutlet weak var breakfastLabel: UILabel!
#IBOutlet weak var lunchLabel: UILabel!
#IBOutlet weak var totalCaloriesLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let navController = segue.destination as! UINavigationController
let destController = navController.topViewController as! SearchViewController
destController.delagate = self
}
#IBAction func addBreakfastPressed(_ sender: UIButton) {
}
#IBAction func addLunchPressed(_ sender: UIButton) {
}
}
extension LogViewController: DataDelegate{
func updateLogCalories(str: String?) {
breakfastLabel.text = str
}
}
If all of your buttons (breakfast, lunch, and dinner) trigger the addButtonPressed action, you need a way to tell which button was pressed, and a way to pass that information to the DataDelegate.
I suggest you put your buttons into an array:
#IBOutlet weak var breakfastButton: UIButton!
#IBOutlet weak var lunchButton: UIButton!
#IBOutlet weak var dinnerButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Populate our array of buttons so we can search for a button
buttons = [breakfastButton, lunchButton, dinnerButton]
}
Then modify your DataDelegate protocol to include a meal enum:
enum Meal: Int {
case breakfast = 0
case lunch = 1
case dinner = 2
}
protocol DataDelegate {
func updateLogCalories(str: String?, forMeal meal: Meal)
}
And set up your DataDelegate to implement the new method:
class MyDataDelegate: DataDelegate {
func updateLogCalories(str: String?, forMeal meal: Meal) {
let str = str ?? ""
print("updating calories with string \(str) for meal \(meal)")
}
}
Now modify your addButtonPressed method so it searches the array to figure out which button was pressed.
#IBAction func addButtonPressed(_ sender: UIButton) {
if let index = buttons.firstIndex(of: sender),
let meal = Meal(rawValue: index) {
print("Button at index \(index) pressed")
delegate.updateLogCalories(str: nil, forMeal: meal)
} else {
print("Can't find button or can't create enum.")
}
}
how do you pass data from search Bar to UILabel in a different view controller
Passing Data between View Controllers
I have tried multiple questions here on stack, but it just doesn't work or the simulator ends up crashing for me
class FirstVC: UIViewController, DataEnteredDelegate {
#IBOutlet weak var label: UILabel!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSecondViewController" {
let secondViewController = segue.destination as! SecondVC
secondViewController.delegate = self
}
}
func userDidEnterInformation(info: String) {
label.text = info
}
}
// protocol used for sending data back
protocol DataEnteredDelegate: class {
func userDidEnterInformation(info: String)
}
class SecondVC: UIViewController {
// making this a weak variable so that it won't create a strong reference cycle
weak var delegate: DataEnteredDelegate? = nil
#IBOutlet weak var searchBar: UISearchBar!
#IBAction func sendTextBackButton(sender: AnyObject) {
// call this method on whichever class implements our delegate protocol
delegate?.userDidEnterInformation(info: searchBar.text!)
// go back to the previous view controller
_ = self.navigationController?.popViewController(animated: true)
}
}
I don't find anything suspicious in your code which cause app crash. Meanwhile for data transfer between view controller we can use delegation and NotificationCenter. Below code uses NotificationCenter
let kStringTransfer = "StringTransferNotification"
class FirstViewController: UIViewController, DataEnteredDelegate {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(setString(notify:)), name: NSNotification.Name(rawValue: kStringTransfer), object: nil)
}
func getSecondVC() {
if let second = self.storyboard?.instantiateViewController(withIdentifier: "Second") as? ViewController {
self.navigationController?.pushViewController(second, animated: true)
}
}
#IBAction func searchBarBtn(_ sender: Any) {
//go to search view controller
getSecondVC()
}
#objc func setString(notify: Notification) {
//set search bar string to text field
textField.text = notify.object as? String
}
}
class SecondViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
searchBar.delegate = self
}
#IBAction func goBackBtn(_ sender: Any) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: kStringTransfer), object: searchBar.text)
self.navigationController?.popViewController(animated: true)
}
}
When I need pass just small values between 2 viewControllers, I don't use a delegate, just create a variable in second view and pass like this.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "showView") {
let vc = segue.destination as! secondViewController
vc.stringTemp = "My string"
}
}
I have this code:
MainViewControler:
class MainViewControler: UIViewController, ContainerToMaster {
#IBOutlet weak var systemContainerView: UIView!
#IBOutlet weak var favoriteProductBtn2: UIButton!
weak var containerViewController: Calculator1ViewController?
func changeBtn() {
favoriteProductBtn2.isHidden = true
print("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
}
}
CallculatorViewController:
#IBOutlet weak var containerView: UIView!
class CallculatorViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
Calculator1ViewController:
protocol ContainerToMaster {
func changeBtn()
}
class Calculator1ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
containerToMaster?.changeBtn()
}
}
I have such an application layout:
Main view = MainViewControler.
In MainViewControler, I have a containerView (systemContainerView) in which the MainViewControler is located
In the CallculatorViewController, I have the next containerView in which the Calculator1ViewController is located.
When entering the Calculator1ViewController the function: containerToMaster?.changeBtn() should start (it should work in MainViewControler).
The problem is - this function does not work :(
Does anyone know how to fix it?
A contained viewController is embedded with an embed segue. You need to override prepare(for:sender) and set self as the containerToMaster delegate in the destination viewController. Your situation is complicated by the fact that you have a container view embedded in another container view, so you'll need to set up two delegates and pass the button call back:
protocol ContainerToMaster {
func changeBtn()
}
class MainViewControler: UIViewController, ContainerToMaster {
#IBOutlet weak var systemContainerView: UIView!
#IBOutlet weak var favoriteProductBtn2: UIButton!
weak var containerViewController: Calculator1ViewController?
func changeBtn() {
favoriteProductBtn2.isHidden = true
print("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destination as? CallculatorViewController {
destVC.containerToMaster = self
}
}
}
class CallculatorViewController: UIViewController, ContainerToMaster {
weak var containerToMaster: ContainerToMaster?
override func viewDidLoad() {
super.viewDidLoad()
}
func changeBtn() {
// pass the button call back
containerToMaster?.changeBtn()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destination as? Calculator1ViewController {
destVC.containerToMaster = self
}
}
}
class Calculator1ViewController: UIViewController {
weak var containerToMaster: ContainerToMaster?
override func viewDidLoad() {
super.viewDidLoad()
containerToMaster?.changeBtn()
}
}
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
}
}