Andre
Andre

Reputation: 1

Delegate returning nil even though delegate has been set

new to stackoverflow so please let me know if extra context needs to be provided but I have an xcode project (fitness app) and in it I have a tab bar controller , two view controllers called ProfileViewController and CalorieViewController that has no segue relationship, but are part of the same tab bar. I want to pass total calories from the calorieVC to profileVC to do some calculations (totalCalories/dailyValue) and i have opted to use delegate pattern which I followed according to some videos and textbook but in my console I it says my delegate is nil.

So far I have define the Delegate Protocol:

protocol CalorieViewDelegate: AnyObject {
    func didUpdateTotalCalories(_ getTotalCalories: Double)
}

Step 2: Create a Delegate Property:

var delegate: CalorieViewDelegate?
var totalCalories: Double = 0.0 {
    didSet {
        delegate?.didUpdateTotalCalories(getTotalCalories: totalCalories)
    }
}

Step 3: Implement the Delegate Method ():

func updateTotalCalories() {
    let totalCalories = breakfastFoods.reduce(0, { $0 + $1.calories }) +
                        lunchFoods.reduce(0, { $0 + $1.calories }) +
                        dinnerFoods.reduce(0, { $0 + $1.calories }) +
                        snackFoods.reduce(0, { $0 + $1.calories })

    delegate?.didUpdateTotalCalories(getTotalCalories : totalCalories)

    totalCaloriesLabel.text = "\(totalCalories)"
    print(totalCalories)
    if let delegate = delegate {
        delegate.didUpdateTotalCalories(getTotalCalories: totalCalories)
        print("Delegate called with totalCalories: \(totalCalories)")
    } else {
        print("Delegate is nil, cannot call didUpdateTotalCalories")
    }
}

Step 4: Set the Delegate (ProfileViewController):

var calories: Double = 0.0
var dailyValue: Double = 2000.0
extension ProfileViewController: CalorieViewDelegate {
    func didUpdateTotalCalories(getTotalCalories: Double) {
        
        calories = getTotalCalories
        print(calories)
    }
    
}

Here is some more relevant code from ProfileVC:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    let calorieVC = CalorieViewController()
    calorieVC.delegate = self
}

My project builds but I need to pass that data over to finish the last calculations in the profileVC and I can't find a reason why it won't do that.

Upvotes: 0

Views: 67

Answers (2)

DonMag
DonMag

Reputation: 77486

Relying on code like this:

let calorieTab = self.tabBarController?.viewControllers?[4] as! CalorieViewController

can be error-prone.

Instead of saying "I want to pass data from tab4 back to tab1", try thinking in terms of "I want to share data among the tabs."

Several ways to do that...

Both examples will use this simple setup:

enter image description here

Each tab's view controller has a Label and a Button -- we'll increment a counter on every button tap, and update the label with the count.


First, we can subclass UITabBarController and track our data in that class:

class MyCustomTabBarController: UITabBarController {
    
    var myCounter: Int = 0
    
}
class Tab1VC: UIViewController {
    
    @IBOutlet var counterLabel: UILabel!

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // make sure we've setup the hierarchy properly
        guard let tabController = self.tabBarController as? MyCustomTabBarController
        else { return }
        counterLabel.text = "Counter: \(tabController.myCounter)"
    }
    @IBAction func btnTapped(_ sender: Any) {
        // make sure we've setup the hierarchy properly
        guard let tabController = self.tabBarController as? MyCustomTabBarController
        else { return }
        tabController.myCounter += 1
        counterLabel.text = "Counter: \(tabController.myCounter)"
    }
    
}
class Tab2VC: UIViewController {
    
    @IBOutlet var counterLabel: UILabel!
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // make sure we've setup the hierarchy properly
        guard let tabController = self.tabBarController as? MyCustomTabBarController
        else { return }
        counterLabel.text = "Counter: \(tabController.myCounter)"
    }
    @IBAction func btnTapped(_ sender: Any) {
        // make sure we've setup the hierarchy properly
        guard let tabController = self.tabBarController as? MyCustomTabBarController
        else { return }
        tabController.myCounter += 1
        counterLabel.text = "Counter: \(tabController.myCounter)"
    }

}

Assign MyCustomTabBarController as the custom class of the tab bar controller. As you can see from the code, we're using a var in the tab bar controller instead of in each view controller.


Another approach is to use a "Data Manager" class...

Very simple example:

class CalorieData: NSObject {
    var totalCalories: Int = 0
    
    static let shared: CalorieData = {
        let instance = CalorieData()
        // any necessary setup code
        return instance
    }()
}

We don't set a custom class for the UITabBarController, and our tabs become:

class Tab1VC: UIViewController {
    
    @IBOutlet var counterLabel: UILabel!
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        counterLabel.text = "Counter: \(CalorieData.shared.totalCalories)"
    }
    @IBAction func btnTapped(_ sender: Any) {
        CalorieData.shared.totalCalories += 1
        counterLabel.text = "Counter: \(CalorieData.shared.totalCalories)"
    }
    
}
class Tab2VC: UIViewController {
    
    @IBOutlet var counterLabel: UILabel!
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        counterLabel.text = "Counter: \(CalorieData.shared.totalCalories)"
    }
    @IBAction func btnTapped(_ sender: Any) {
        CalorieData.shared.totalCalories += 1
        counterLabel.text = "Counter: \(CalorieData.shared.totalCalories)"
    }

}

Couple advantages to the "Data Manager" approach, but most significantly -- we can access that data from anywhere. If you decide to add a form-type-view to you UI that you want to .present(...) instead of it being a complete ViewController in another Tab, no problem.

Upvotes: 0

Rajni Bajaj
Rajni Bajaj

Reputation: 140

You can also Resolve the Problem by using notification Observer instead of Delegate Methods Like, when you update the total calories you can post the Notification Observer there using the code. You can also pass the Data using Userinfo like :

  let caloriesData:[String: String] = ["calories": "your Value"]

   NotificationCenter.default.post(name: "Updatecalories", object: 
  nil,userInfo: caloriesData)

and where you want to act like in some other class you can add the following code in ViewDidLoad() Method :

NotificationCenter.default.addObserver(
        self,
        selector: #selector(updateCaloriesValues(_:)),
        name: "Updatecalories",
        object: nil
    )

and add the following Objective method in the same class

@objc func updateCaloriesValues(_ notification: Notification){
 
   if let caloriesValue = notification.userInfo?["calories"] as? String 
   {
      //Do your functionality here 
   }
 }

you can Modify the code According to your needs.

Upvotes: 1

Related Questions