Reputation: 1
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
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:
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
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