Delegate in Swift-language - ios

I have two controllers and i need call up function the first controller to second controller:
In second controller I have created protocol and init delegate in class:
protocol testProtocol {
func testDelegate() // this function the first controllers
}
class SecondViewController: UIViewController {
var delegate: testProtocol?
....
}
#IBAction func testDelegateClicked(sender : AnyObject) {
delegate?.testDelegate()
}
First Controller
class ViewController: UIViewController, testProtocol {
var secondController: SecondViewController = SecondViewController()
override func viewDidLoad() {
super.viewDidLoad()
secondController.delegate = self
}
func testDelegate() {
println("Hello delegate")
}</pre>
But function not getting called

I am going to make an assumption you are using storyboards. If I am correct, then your issue is that your secondController, created in your First Controller, is not the actual one you are presenting. You will need to set secondController in your prepareForSegue:
Second Controller
Unchanged
First Controller
class ViewController: UIViewController, testProtocol {
// you will want to add the ? since this variable is now optional (i.e. can be nil)
var secondController: SecondViewController? // don't assign it a value yet
// ...
// implementation of the protocol
func testDelegate() {
println("Hello delegate")
}
// your prepare for segue
override func prepareForSegue(segue: UIStoryboardSegue?, sender: AnyObject?) {
// get the controller that storyboard has instantiated and set it's delegate
secondController = segue!.destinationViewController as? SecondViewController
secondController!.delegate = self;
}
}

Related

Update label in ViewControllerB from ViewControllerA (ViewControllers are both in containers in the same view)

View controllers A and B are both in containers and together form a single view.
In ViewControllerA I have a button and a label, and in ViewControllerB I have a label.
Both labels are initialised to the number "5".
By pressing on the button in ViewControllerA I would like to add 3 to each label,
i.e. each label should then display "8".
I thought it was as simple as defining a function in ViewControllerB to accept the updated total from ViewControllerA, and to then update the text property of the label in ViewControllerB.
Of course, I get "unexpectedly found nil while unwrapping an Optional value".
Advice/guidance greatly appreciated.
import UIKit
class ViewControllerA: UIViewController {
//MARK: Properties
#IBOutlet weak var buttonInViewControllerA: UIButton!
#IBOutlet weak var labelInViewControllerA: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: Actions
#IBAction func buttonActionInViewControllerA(_ sender: UIButton) {
let a: String = String(Int(labelInViewControllerA.text!)! + 3)
labelInViewControllerA.text = a
ViewControllerB().add3(value: a)
}
}
class ViewControllerB: UIViewController {
//MARK: Properties
#IBOutlet weak var labelInViewControllerB: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func add3(value: String) {
self.labelInViewControllerB.text = value
}
}
The problem is that in
#IBAction func buttonActionInViewControllerA(_ sender: UIButton) {
// ...
ViewControllerB().add3(value: a)
}
you create a new instance of ViewControllerB. What you need is a reference (property) to the existing one, which you then inform about the change:
class ViewControllerA: UIViewController {
var controllerB:ViewControllerB?
// ...
#IBAction func buttonActionInViewControllerA(_ sender: UIButton) {
// ...
controllerB?.add3(value: a)
}
}
And dont't forget to set controllerB somewhere in your code, e.g.
var vcA = ViewControllerA()
var vcB = ViewControllerB()
vcA.controllerB = vcB
// dispaly vcA and vcB
You likely want to use the Protocol / Delegate pattern (good idea to read up on it if you're not familiar).
Basically, you create a Protocol that defines a function (or functions) that you want to access from "somewhere else."
For example:
// protocol / delegate pattern
protocol ABDelegate: class {
func myButtonWasTapped()
}
You want to call myButtonWasTapped() from the button tap in A, so you create a view controller that "conforms" to this protocol (it has that func), and an ABDelegate variable in the view controller where you want to call the func.
Your first thought might be:
"ok, I'll set that up so vcB is the delegate for vcA and have my button tap func in vcA call the increment func in vcB directly."
That can work, but then the two classes are "too tightly coupled." That is, too dependent on each other.
The better approach is to have your "main" view controller act as an intermediary:
// this will be called by the code in ViewControllerA
func myButtonWasTapped() {
// call func in ViewControllerB
vcB?.doIncrement()
}
So it looks like this:
The tricky part is getting references to vcA and vcB. Because the embedded view controllers in container views are loaded via embed segues you can grab them in prepare(for segue:...):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// if this is the embedded ViewControllerA
if let vc = segue.destination as? ViewControllerA {
// set self as the ABDelegate in ViewControllerA
vc.theDelegate = self
// keep a reference in case we want to access it
self.vcA = vc
}
// if this is the embedded ViewControllerB
if let vc = segue.destination as? ViewControllerB {
// keep a reference so we can call its doIncrement() func
self.vcB = vc
}
}
The full code becomes:
//
// ContainerTestViewController.swift
//
import UIKit
// protocol / delegate pattern
protocol ABDelegate: class {
func myButtonWasTapped()
}
// "main" view controller conforms to ABDelegate protocol
class ContainerTestViewController: UIViewController, ABDelegate {
var vcA: ViewControllerA?
var vcB: ViewControllerB?
// this will be called by the code in ViewControllerA
func myButtonWasTapped() {
// call func in ViewControllerB
vcB?.doIncrement()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// if this is the embedded ViewControllerA
if let vc = segue.destination as? ViewControllerA {
// set self as the ABDelegate in ViewControllerA
vc.theDelegate = self
// keep a reference in case we want to access it
self.vcA = vc
}
// if this is the embedded ViewControllerB
if let vc = segue.destination as? ViewControllerB {
// keep a reference so we can call its doIncrement() func
self.vcB = vc
}
}
}
class ViewControllerA: UIViewController {
var theDelegate: ABDelegate?
#IBAction func incTapped(_ sender: Any) {
// call the func in the delegate
theDelegate?.myButtonWasTapped()
}
}
class ViewControllerB: UIViewController {
#IBOutlet var theLabel: UILabel!
var theValue = 0
override func viewDidLoad() {
super.viewDidLoad()
theLabel.text = String(theValue)
}
func doIncrement() -> Void {
theValue += 3
theLabel.text = String(theValue)
}
}

Protocols are not working in Swift (Getting nil)

Refernce image Im new to protocols, I tried the following implementation to achieve the protocols, but im getting nil value for the protocol object
import UIKit
//MARK: step 1 Add Protocol here.
protocol MyDelegate: class {
func changeBackgroundColor(_ color: UIColor?)
}
class ViewController: UIViewController {
//MARK: step 2 Create a delegate property here.
weak var delegate: MyDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//MARK: step 3 Add the delegate method call here.
delegate?.changeBackgroundColor(UIColor.red)
}
}
Here I am getting delegate value nil and protocol not getting called.
here is the implementation
import UIKit
class HomeViewController: UIViewController, MyDelegate {
func changeBackgroundColor1(_ color: UIColor?) {
self.view.backgroundColor = color
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
callVC()
}
func callVC() {
let vc = ViewController()
vc.delegate = self
}
func changeBackgroundColor(_ color: UIColor?) {
self.view.backgroundColor = color
}
In short:
You have forgotten to set the delegate to the object that conforms the protocol.
Detailed Answer:
Protocol is an agreement between two objects so that you know a delegate will have some specified functions ready to be called. Let's explain with an example:
Suppose that you have two view controllers called A and B. You have defined your protocol in global (as you did in your code). Then you create a property called delegate in A which will hold a weak reference of any object conforms the protocol. In this case, this held object reference is B.
So you need to have a property in A like below:
weak var delegate: MyDelegate?
Then determine this delegate to be the reference of what you need. In our example, it's B. So you need to set it in B as below.
// Somewhere you have the reference of the object or where you initialize it.
instanceOfA.delegate = self
Finally you conform the protocol in B like:
extension B: MyProtocol {
func changeBackgroundColor(_ color: UIColor?) {
// some implementation goes here
}
}
There you are. Now, you can make sure that you have delegate object and protocol methods are getting called if you have completed steps above correctly.
The problem lies in your callVC method:
func callVC()
{
let vc = ViewController()
vc.delegate = self
}
You create an instance of your ViewController, but you are not doing anything with it. No methods, aside from initializer, will be called on that view controller because it's not a part of active navigation stack. Also, since it's just a local variable (not retained anywhere) it will be deallocated immediately after leaving the method scope.
What you need to do is to present the view controller somehow - either with a navigation controller, or as a child to the current view controller
func callVC()
{
let vc = ViewController()
vc.delegate = self
addChildViewController(vc)
view.addSubview(vc.view)
vc.didMove(toParentViewController: self)
}
or if you're using navigation controller
func callVC()
{
let vc = ViewController()
vc.delegate = self
navigationController?.pushViewController(vc, animated: true)
}
Hi Smart i think the problem is when you set the delegate
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
callVC()
}
func callVC() {
let vc = ViewController()
vc.delegate = self
}
Because you set the delegate for the class ViewController, but you don't present the viewController at all; you should set the delegate before presenting the ViewController, one way to do it is to present the ViewController and in function callVc add this
func callVC() {
let vc = ViewController()
vc.delegate = self
//Present the viewController with this
present(vc, animated: true, completion: nil)
}
Your optional delegate variable is nil. You need to set it first from the UIViewController you are segueing from. For example.
class OtherViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.performSegue(withIdentifier: "addHereTheSegueIdFromStoryBoard", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "addHereTheSegueIdFromStoryBoard" {
guard let vc = segue.destination as? ViewController else {return}
vc.delegate = self
}
}
}
import UIKit
class ViewController: UIViewController {
weak var delegate: MyDelegate?
override func viewDidLoad() {
super.viewDidLoad()
if delegate != nil {
delegate?.changeBackgroundColor(UIColor.red)
}
}
}
Call like
class SecondViewController: UIViewController(), MyDelegate {
func callVC() {
let vc = ViewController()
vc.delegate = self
}
func changeBackgroundColor(_ color: UIColor?) {
//Code here
}
}

swift delegate function is not called

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.

Access the same variable before and after value changes in different classes - Swift

I've stucked on a simple concept(I guess), basically I have two ViewControllers on Storyboard also I have 2 classes, ViewController and ViewController2:
I Have a Label whit a default value (0), and when I click on button I want to change the value for this variable to 10, and then I click on the button "Show" and I print this variable, I'm successfully changing the Label and printing the new Value.
The real problem is when I want to get the new variable value from another view, even after I change the value if I try to print the variable on second view the variable always return de default value(0)
ViewController
import UIKit
class ViewController: UIViewController {
var variable = "0"
#IBOutlet var defaultLabel: UILabel!
#IBOutlet var label1Label: UILabel!
#IBAction func setValue(sender: AnyObject) {
setValue()
}
#IBAction func getValue(sender: AnyObject) {
getValue()
}
override func viewDidLoad() {
super.viewDidLoad()
}
func setValue(){
variable = "10"
defaultLabel.text = variable
}
func getValue(){
print(variable)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
ViewController2
import UIKit
class ViewController2: UIViewController {
#IBOutlet var label2Label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func show(sender: AnyObject) {
print(ViewController().getValue())
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I've found this post:
Access variable in different class - Swift
And I really think this is the way that I will find my solution but I really don't understand how to call the variable on ViewController2.
Thanks.
#IBAction func show(sender: AnyObject) {
print(ViewController().getValue())
}
ViewController() - this is class constructor and each time you call ViewController() it return a new instance/object of ViewController class, with default values of course.
If you show ViewController2 from ViewController you can create a property/variable variable2 like variable in ViewController and set value before display, but after ViewController2 is created. If you use segues you can put this code in ViewController class:
// Put this code in ViewController class
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let viewController2 = segue.destinationViewController as? ViewController2
if viewController2 != nil {
// you can't set the value for label at this time
// because the viewcontroller and all its UI controls aren't loaded
// but you can set a non UI variable
viewController2?.variable2 = self.variable
}
}
After that you can put one line of code in viewDidLoad method from ViewController2 class:
// Put this code in ViewController2 class
var variable2 = "0"
#IBOutlet var label2Label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.label2Label.text = variable2
}
Use Delegates!
Here's an example where ViewController1 is the delegate for ViewController2:
Define a protocol:
protocol VariableManager {
func getValue() -> Int
}
Then, in ViewController1, modify the getValue method so that ViewController1 conforms to the protocol:
class ViewController1: VariableManager {
func getValue() -> String {
return variable
}
}
Now define a variable in ViewController2 named delegate:
class ViewController2 {
var delegate: VariableManager?
}
In your prepareForSegue method in ViewController1 :
override func prepareForSegue(segue: UIStoryboardSegue) {
if let identifier = segue.identifier {
switch identifier {
case "MySegueIdentifier":
let destination = segue.destinationViewController as! 'ViewController2'
destination.delegate = self
default:
break
}
}
}
Now in ViewController2, change the show method:
#IBAction func show(sender: AnyObject) {
if let delegate = delegate {
let variable = delegate.getValue()
print(variable)
}
Delegation is a very common, and very important pattern. I suggest you read up on it: https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html
Trying to instantiate another instant of ViewController1 inside ViewController2 is not good practice.

Sending data with Segue with Swift

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

Resources