Protocol and Delegate Help? Value isn't passing back - ios

I've got my EditorViewController that segues modally to my ModalViewController, and in the ModalViewController I have to pass some data back to the EditorViewController after the view is dismissed. I've looked at many tutorials about delegates and protocols, and I believe that's what I have to do to pass this information, but I can't seem to get the code right although I've followed the tutorials exactly. If anyone can see what's going wrong in here I would appreciate it. I'll post the code.
The protocol
protocol passColorBackDelegate {
func colorToChange(_ color: String)
}
The first view Controller
class EditorViewController: UIViewController, passColorBackDelegate {
func colorToChange(_ color: String) {
print("Hello")
}
The second view Controller file (the one with data to pass back to first), also has another class in it, I'm stingy with my files
class subView: UIView {
}
class ModalViewController: UIViewController {
var delegate: passColorBackDelegate?
#IBAction func changeColor(_ sender: UIButton) {
switch sender {
case blueColorButton: colorToChangeTo = "Blue"
case redColorButton: colorToChangeTo = "Red"
case greenColorButton: colorToChangeTo = "Green"
case purpColorButton: colorToChangeTo = "Purple"
default: print("error")
}
print(colorToChangeTo)
delegate?.colorToChange(colorToChangeTo)
self.dismiss(animated: true, completion: nil)
}
As you can see my protocol function doesn't include any of the data I need passed back yet, but the message still isn't printing, meaning the function isn't getting called. If anyone could tell me what I'm doing wrong I would appreciate it. Thanks

You will need to set the delegate before you perform the segue. Since it sounds like you're using Storyboards, this can be done in prepare(for segue):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVC = segue.destination as? ModalViewController {
destinationVC.delegate = self
}
}
Also, as Paul mentioned in the comments, capitalizing your protocols (PassColorBackProtocol) and classes (SubView) is the conventional style in Swift and helps other people understand your code.

Related

delegate remains nil

I'm trying to send data from one ViewController to another with delegate, but can't seem to get the right instance
I've tried setting the delegate at different places within the receiving ViewController including ViewDidLoad, but the delegate in the sending ViewController is always nil.
From what I've learned, it's an average problem everybody seems to go through, and I've read quite a number of samples, tried them, but to no avail. I don't know if I'm leaving something out or not. Please shed some light if you will.
Below is what I ended up with.
The sending ViewController:
protocol CreateChatDelegate: class{
func appendChatData(_ sender: CreateChatViewController)
}
class CreateChatViewController: UIViewController {
weak var delegate: CreateChatDelegate!
#IBAction func createChat(_ sender: AnyObject) {
delegate?.appendChatData(self)
if delegate == nil {
print("delegate unsuccessful")
} else {
print("delegate successful")
}
}
The receiving ViewController:
class ViewController: UIViewController{
var createChatViewController: CreateChatViewController!
override func viewDidLoad() {
super.viewDidLoad()
...
}
}
extension ViewController: CreateChatDelegate {
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// get the reference to the ViewController
self.createChatViewController = segue.destination as? CreateChatViewController
// set it as delegate
self.createChatViewController?.delegate = self
print("ViewController: delegate successful")
}
}
func appendChatData(_ sender: CreateChatViewController) {
print("ViewController: CreateChatDelegate called")
}
}
this code outputs "delegate unsuccessful", because delegate is always nil
The method you are using is incorrect. You should use the new one:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
....
}
Notice the override keyword? if you don't see this when you are writing the viewController methods, It means that you are NOT calling the original method and misspelled the function signature.
NOTE: if you are targeting older iOS and using older Xcode, the method name may be different, you should write the name of the method and let the AutoComplete help you with the correct one.
To successsfuly configure segue you need to make sure that
1- Navigation is triggered by
self.performSegue(withIdentifier:"segue",sender:nil)
2- Force-unwrap
self.createChatViewController = segue.destination as! CreateChatViewController
as as? cast may fail silently for some reason
First make sure that prepareForSegue() method is called. Then make sure that it is called for the CreateChatViewController i.e.
if let destination = segue.destination as? CreateChatViewController {
//Do all the stuff there e.g.
destination.delegate = self
}
If your prepareForSegue() method is not called then set the action properly so it will fire the prepareForSegue() method then you will get the delegate value in the CreateChatViewController.

Where to set delegate in swift?

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?

Make calling ViewController accessible to called VC

I have two VCs in my project. I have a UIButton that segues to the second VC. I have data being sent to this VC. I want the second VC to be able to add to the array that is sent and then send it back.
In my main VC I have:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let toViewController = segue.destination as! SaveViewController
toViewController.masterView = self
In the second VC I have:
var masterView:UIViewController!
...
override func viewWillDisappear(_ animated: Bool) {
masterView.listArray = listArray
}
What I am getting is
Value of type 'UIViewController' has no member 'listArray.'
The listArray is declared in both VCs. If this is a correct way to go about doing what I am trying to do, I am obviously assuming that I must do some more configuring in the second ViewController in order to make the other VC accessible.
Probably this is not the right way to pass data back the the previous view controller. Although there are other options that you can follow to achieve the desired functionality, I would recommend to follow the Delegation pattern approach.
For your case, you could do it like this -for instance-:
According to "How to Apply Delegation?" section in this answer, the first thing that we should do is to implement the needed protocol:
protocol SaveViewControllerDelegate: class {
// I assumed that 'listArray' is an array of strings, change it to the desired type...
func saveViewControllerWillDisappear(_ listArray: [String], viewController: UIViewController)
}
Thus in SaveViewController, you should create -weak- instance of SaveViewControllerDelegate and call its method at for the desired behavior:
class SaveViewController: UIViewController {
weak var delegate: SaveViewControllerDelegate? = nil
var listArray: [String]!
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// assuming that you already did the required update to 'listArray'
// you would need to pass it here:
delegate?.saveViewControllerWillDisappear(listArray, viewController: self)
}
}
So far we added the necessary code for the SaveViewController, let's jump the the MasterViewController (first view controller):
Next, you would need to conform to SaveViewControllerDelegate, Connecting the delegate object and implement its method (steps from 2 to 4 in the mentioned answer):
class MasterViewController: UIViewController, SaveViewControllerDelegate {
var listArray: [String]!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let toViewController = segue.destination as! SaveViewController
// make sure to add this:
toViewController.delegate = self
toViewController.listArray = self.listArray
}
func saveViewControllerWillDisappear(_ listArray: [String], viewController: UIViewController) {
print("here is my updated array list: \(listArray)")
}
}
At this point, saveViewControllerWillDisappear method should be get called when coming back from SaveViewController, including listArray as a parameter.
Aside note:
The reason of the error that you are facing is that you are declaring masterView as UIViewController, what you should do instead is:
var masterView:MasterViewController!
HOWEVER keep in mind that this approach still -as I mentioned before- inappropriate one.
This happens because UIViewController has no element listView.
Change MasterView type to:
var masterView: FirstViewController!

Delegate/Protocols Passing data from one view controller to another

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.

How to call a a different view by code in xcode 7

I am new to xcode 7 and it would be great if you could help me with something.
I have two ViewControllers and I would like to call ViewController2 from ViewController1. I need to do that by code. (I would like to call it from the viewDidLoad()-function)
Thanks in advance
Depending on how you have the project and your navigation setup, here is one of the many ways to accomplish what I think you are asking.
If you are using Segues for navigation, in the ViewController1 (That initialized the navigation) you would override the prepareForSegue function cast your segue.destination as ViewController2 and set the property. Like this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard let viewController2 = segue.destination as? ViewController2 else { return }
viewController2.propertyName = "Property Value"
}
Or, if you are wanting to instantiate ViewController2 in ViewController1 you could do this:
override viewDidLoad() {
super.viewDidLoad()
guard let viewController2 = self.storyboard?.instantiateViewController(withIdentifier: "ID for ViewController (set in the Storyboard)") as? ViewController2 else { return }
viewController2.propertyName = "Property Value"
self.present(viewController2, animated: true, completion: nil)
// Or you could do if you want a full navigation, not just a modal.
self.show(viewController2, sender: self)
}
If you are wanting to access each view controller, you could do something like this:
class ViewController2: UIViewController {
weak var viewController1: ViewController1? //The 'weak' keyword is incredibly important so that you don't have a memory leak.
override viewDidLoad() {
self.viewController1.propertyName = "Property Value"
}
}
Using the same steps as above, you can set the viewController1 property in the ViewController2 instance.
Now I hope that answers your question. Your question is a little vague, so I am shooting in the dark.

Resources