SWIFT - calling function from 1 view controller to another

Looking for some simple help as I just learning SWIFT.


I have a sample app with 5 viewcontrollers (it's a gym app) will each viewcontroller focused on a different workout. In each viewcontroller, I want to capture the same data (weight lifted) but for each workout. I am using CoreData which is working great.


I want to use one function (to say capture the weight) and pass in parameters. If I place this function in viewController1 - how do I call it from viewController2?


Thanks (in advance)
-tekgeek


************* my current viewcontroller with function *****************


import UIKit

import CoreData


class Controller1: UIViewController {


@IBOutlet weak var newMaxLiftText: UITextField!

@IBOutlet weak var newMaxLiftLabel: UILabel!

@IBOutlet weak var maxWeightLiftedLabel: UILabel!

@IBAction func newWeightButon(_ sender: UIButton) {

maxWeightLiftedLabel.text = newMaxLiftText.text

updateData()

}

@IBAction func delerteDataBtton(_ sender: UIButton) {

deleteData()

}

@IBAction func loadDataButton(_ sender: UIButton) {

createData()

}


override func viewDidLoad() {

super.viewDidLoad()

}



////////////// Update Function ////////////////

func updateData(){

guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }

let managedContext = appDelegate.persistentContainer.viewContext

let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "Chest")

fetchRequest.predicate = NSPredicate(format: "name = %@", "Dumbell Chest Press")

do

{

let test = try managedContext.fetch(fetchRequest)

let objectUpdate = test[0] as! NSManagedObject

objectUpdate.setValue(maxWeightLiftedLabel.text!, forKey: "weight")

do{

try managedContext.save()

}

catch

{

print("Error: ",error)

}

}

catch

{

print(error)

}

}

Replies

You have several ways :


1. if you go from VC1 to VC2 by segue, use delegation:


Define a protocol WeightProtocol with the func getWeightValue()

protocol WeightProtocol {
    func getWeightValue() -> Float
}


In viewController1, implement the protocol

class ViewController1: UIViewController, WeightProtocol {

    func getWeightValue() -> Float {
           return theWeightYouHaveDefined
    }

}


Create a delegate in destination VC (ViewController2) , of type WeightProtocol?


    var delegate: WeightProtocol?


In VC2, call delegate?.getWeightValue()

        let tweightFromVC1 = delegate?.getWeightValue()


in viewController1, in prepare for segue, set destinationVC.delegate to self.


    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destination = segue.destination as? ViewController2 {
            destination.delegate = self
          // Other initializations as needed
        }
    }


This mechanism allows you also to change a value in VC1 from VC2 (create a setWeightValue(weight: Float) in protocol)


2. Define global value, in a singleton


3. Save value in persistent storage where you can read back

So the WeightProtocol would be added to VC1 or create a new file?

You can put the declaration in VC1 file, outside of class definition.

Thanks but something is still not clicking with me. It looks like you have rally helped me address the parameter passing part of my problem and I thank you for that. But..

- it is still not clear to me how to make a call to one function in VC1 from VC2


Sorry for being dense...


-tekgeek1

Well, that's easy with delegation.


Just a simple case for testing :


You have an existing func in VC1 that you want to be able to call from VC2

    func testFunction() {
        print("I can be called from VC2")
    }

Add it to the protocol declaration


protocol WeightProtocol {
    func getWeightValue() -> Float

    func testFunction()
}


You can now call it in VC2


        delegate?.testFunction()

Thanks! In VC2 when I make that call I get a compile error saying


Use of unresolved identifier 'delegate'

You probably did not follow all the steps of my first reply :


Create a delegate in destination VC (ViewController2) , of type WeightProtocol?


    var delegate: WeightProtocol?

I get an error now "Cannot convert value of type 'String.Type' to expected argument type 'String'"


This is my VC1 where the function getDataGlobal is the one I want to call . from other ViewControllers

import UIKit
import CoreData

protocol WeightProtocol {
    func updateDataGlobal(EntityVar: String, ExeriseNameVar: String)
}

class ViewController: UIViewController {
    
    @IBOutlet weak var newMaxLiftText: UITextField!
    @IBOutlet weak var newMaxLiftLabel: UILabel!
    @IBOutlet weak var maxWeightLiftedLabel: UILabel!

    
    @IBOutlet weak var DietButton: UIButton!
    @IBOutlet weak var WorkoutButton: UIButton!
    @IBOutlet weak var OtherButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    func updateDataGlobal(EntityVar: String, ExeriseNameVar: String){
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let managedContext = appDelegate.persistentContainer.viewContext
        
        let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: EntityVar)
        fetchRequest.predicate = NSPredicate(format: "name = %@", "Dumbell Chest Press")
        do
        {
            let test = try managedContext.fetch(fetchRequest)
            
            let objectUpdate = test[0] as! NSManagedObject
            objectUpdate.setValue(maxWeightLiftedLabel.text!, forKey: "weight")
            do{
                try managedContext.save()
                print ("Record Updated")
            }
            catch
            {
                print("Error: ",error)
            }
        }
        catch
        {
            print(error)
        }
    }

    
}


This is the ViewContoller making the call to VC1 (getDataGlobal)

import UIKit
import GoogleMobileAds
import CoreData


extension UIViewController {
    
    func HideKeyboard() {
        let Tap:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(DismissKeyboard))
        view.addGestureRecognizer(Tap)
    }
    @objc func DismissKeyboard() {
        view.endEditing(true)
    }
}

class DumbbellChestPress: UIViewController {
    var bannerView: GADBannerView!
    var delegate: WeightProtocol?

    @IBOutlet weak var newMaxLiftText: UITextField!
    @IBOutlet weak var newMaxLiftLabel: UILabel!
    @IBOutlet weak var maxWeightLiftedLabel: UILabel!
    @IBAction func newWeightButon(_ sender: UIButton) {
        maxWeightLiftedLabel.text = newMaxLiftText.text
        delegate?.updateDataGlobal(EntityVar: String, ExeriseNameVar: String)
//        updateData()
    }

There are at least 2 problems.


1. How do you transition from VC1 to VC2 ? segue ?

You do not set the delegate of destination VC in VC1, with some code like


    override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 
        if let destination = segue.destination as? ViewController2 { 
            destination.delegate = self 
          // Other initializations as needed 
        } 
    }



Second, when you call line 26

delegate?.updateDataGlobal(EntityVar: String, ExeriseNameVar: String)


You need to give the real arguments values


       delegate?.updateDataGlobal(EntityVar: someValue, ExeriseNameVar: otherValue)

One last take at this since I dont want to waste anymore of your time - but I am learning more each day.... something is still not cool. Here is what I have now - the updateDataGlobal func is not being executed.


Also, this code will (eventually) allow VC2 to call a func in VC1. Is there a way to allow say VC3 and VC4 to make the same call to VC1?


ViewController1 - ViewController.swift

//
//  ViewController.swift
//

import UIKit
import CoreData

protocol WeightProtocol {
    func updateDataGlobal(EntityVar: String, ExeriseNameVar: String)
}

class ViewController: UIViewController {
    
    @IBOutlet weak var newMaxLiftText: UITextField!
    @IBOutlet weak var newMaxLiftLabel: UILabel!
    @IBOutlet weak var maxWeightLiftedLabel: UILabel!

    
    @IBOutlet weak var DietButton: UIButton!
    @IBOutlet weak var WorkoutButton: UIButton!
    @IBOutlet weak var OtherButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        DietButton.layer.cornerRadius = 10
        DietButton.layer.masksToBounds = true
        DietButton.layer.borderColor = UIColor.white.cgColor
        DietButton.layer.borderWidth = 2
        
        OtherButton.layer.cornerRadius = 10
        OtherButton.layer.masksToBounds = true
        OtherButton.layer.borderColor = UIColor.white.cgColor
        OtherButton.layer.borderWidth = 2
        
        WorkoutButton.layer.cornerRadius = 10
        WorkoutButton.layer.masksToBounds = true
        WorkoutButton.layer.borderColor = UIColor.white.cgColor
        WorkoutButton.layer.borderWidth = 2
        
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destination = segue.destination as? DumbbellChestPress {
            destination.delegate = self as! WeightProtocol
            // Other initializations as needed
        }
    }
    
    func updateDataGlobal(EntityVar: String, ExeriseNameVar: String){
        print (" **** Step 1 complete ***")
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let managedContext = appDelegate.persistentContainer.viewContext
        
        let fetchRequest:NSFetchRequest = NSFetchRequest.init(entityName: EntityVar)
        fetchRequest.predicate = NSPredicate(format: "name = %@", "Dumbell Chest Press")
        do
        {
            let test = try managedContext.fetch(fetchRequest)
            
            let objectUpdate = test[0] as! NSManagedObject
            objectUpdate.setValue(maxWeightLiftedLabel.text!, forKey: "weight")
            do{
                try managedContext.save()
                print ("Record Updated")
            }
            catch
            {
                print("Error: ",error)
            }
        }
        catch
        {
            print(error)
        }
    }

    
}



ViewController2 - DumbbellChestPress

//
//  DumbbellChestPress.swift

import UIKit
import GoogleMobileAds
import CoreData

class DumbbellChestPress: UIViewController {
    var delegate: WeightProtocol?

    @IBOutlet weak var newMaxLiftText: UITextField!
    @IBOutlet weak var newMaxLiftLabel: UILabel!
    @IBOutlet weak var maxWeightLiftedLabel: UILabel!
    @IBAction func newWeightButon(_ sender: UIButton) {
        maxWeightLiftedLabel.text = newMaxLiftText.text
        delegate?.updateDataGlobal(EntityVar: "Chest", ExeriseNameVar: "Dumbell Chest Press")
//        updateData()
    }
    
    @IBAction func delerteDataBtton(_ sender: UIButton) {
        deleteData()
    }
    
    @IBAction func loadDataButton(_ sender: UIButton) {
        createData()
    }
    
    @IBOutlet weak var Workout1: UIImageView!
    @IBOutlet weak var Workout2: UIImageView!
    
    @IBOutlet weak var Workout4: UIImageView!
    @IBOutlet weak var Workout3: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        retrieveData()
    }
    
  

You have not followed everything !


I told you need VC1 () to conform to protocol.


So its declaration must be :

class ViewController: UIViewController, WeightProtocol {

Now, line 49 can be simply

            destination.delegate = self


That should make it work.


For your second question,

Is there a way to allow say VC3 and VC4 to make the same call to VC1?


Yes, do the same:

when you segue (or instanciate) from VC1 to say VC3, declare the delegate:


Typically, your prepare should become :


    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destination = segue.destination as? DumbbellChestPress {
            destination.delegate = self
            // Other initializations as needed
        }
        if let destination = segue.destination as? VC3  {
            destination.delegate = self
            // Other initializations as needed
        }
        if let destination = segue.destination as? VC4  {
            destination.delegate = self
            // Other initializations as needed
        }

  }



Of course, declare a

   var delegate: WeightProtocol?

in VC3 and VC4.


And use as you did in VC2.

As soon as I sent that I realized I messed up - made the change. It now is not processing the


    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destination = segue.destination as? DumbbellChestPress {
            destination.delegate = self
            // Other initializations as needed
        }
    }


I added a print statement and it is failing the IF. I then rewrote it and removed the IF and printed segue.destination and it is not pointing to DubbbellChestPress view controller.


More debugging (maybe I am thinking about this incorrect. I have a VC which is a selection of workouts (Chest, legs, Abs, etc) which points to another VC with 5 workouts which in turn points to another VC the has specific exercises. I have the 'common' function in the ViewController which is the 1st one with a listing of the workouts - I am trying to call this function from a VC3 which has specific exercises.


Grrrrr

Have you thought of creating another Class for the weight capture function?

// WeightCapturer.swift
public class WeightCapturer {
    class func captureWeight(weight: Integer) {
        // do stuff with the weight here
    }
}


Then in View Controller 1 and View Controller 2, you can call it like this

ViewController1 : UIViewController {
    @IBAction func someButton(_ sender: UIButton) {
        WeightCapturer.captureWeight(someWeightValue)
    }
}


ViewController2 : UIViewController {
    @IBAction func someButton(_ sender: UIButton) {
        WeightCapturer.captureWeight(someWeightValue)
    }
}



Hope this helps!