Background
I have a workoutVM
view model that would hold most of my view models, which I want to pass down to all other view controllers in my app. My app also has a tabbar controller, which I have used to store some data such as user information, etc.
Problem 1
Even though I have created the workoutVM
view model (with values) in the MyTrainingViewController
("Home" tab), I am unable to pass the view model (with values) to the next ExerciseSetsViewController
("Workout" tab) by using
MyTrainingViewControllerDelegate
and/orExerciseSetsViewController
and loading the view.The codes/action to pass workoutVM
view model are run when user selects a particular tableview cell.
I am really not sure why either one of the methods work because similar approach worked for many other scenarios. Below is the Xcode debugger showing that my codes to utilize both methods didn't pass the workoutVM view model to the ExerciseSetsViewController
Problem 2
As a result, I found a workaround (Method 3 that were commented out in the below codes) to utilize tabbar to store the workoutVM
view model (again relying on tabbar to pass & share data across multiple view controllers).
At this point, I am afraid that my app is practically using tabbar as a "singleton", even though I "sort of" understand that it is not quite "singleton".
I think, ideally, the view models should serve as some sort of data models, which are to be shared/manipulated/passed across multiple view controllers without the need to have tabbar as a middle layer. Wouldn't that be correct? Or is this the best/good practice that I am adopting by utilizing the tabbar?
protocol MyTrainingViewControllerDelegate {
func passWorkoutVM(workoutVM: WorkoutViewModel)
}
class MyTrainingViewController: UIViewController {
var workoutVM: WorkoutViewModel?
var delegate: MyTrainingViewControllerDelegate!
@IBOutlet weak var dayProgramTableView: UITableView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
dayProgramTableView.delegate = self
dayProgramTableView.dataSource = self
}
}
extension MyTrainingViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let tabbar = tabBarController as! MainTabBarController
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let ExerciseSetsViewController = storyBoard.instantiateViewController(withIdentifier: "ExerciseSetsView") as! ExerciseSetsViewController
guard let workoutVM = self.workoutVM else {
return
}
print("printing workoutViewModel dayprogram no.of exercise at HomeView \(workoutVM.dayprograms[indexPath.row].dayprogram.dayIntensity)")
//1.Instantiate view via storyboard Method
ExerciseSetsViewController.loadViewIfNeeded()
ExerciseSetsViewController.workoutVM = workoutVM
//2.Delegate Method
self.delegate?.passWorkoutVM(workoutVM: workoutVM)
//3.Tabbar Method
// tabbar.workoutVM = workoutVM
tabbar.selectedIndex = 1
}
}
class ExerciseSetsViewController: UIViewController {
var workoutVM: WorkoutViewModel?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
// ** To do
//create workoutViewModel
//3.Tabbar Method
// self.workoutVM = tabbar.workoutVM
print("printing workoutViewModel to check if workoutVM had been passed from MyTrainingView \(String(describing: self.workoutVM))")
}
}
extension ExerciseSetsViewController: MyTrainingViewControllerDelegate {
func passWorkoutVM(workoutVM: WorkoutViewModel) {
self.workoutVM = workoutVM
print("passWorkoutDelegate Method executed")
}
}
class MainTabBarController: UITabBarController {
var workoutVM: WorkoutViewModel?
override func viewDidLoad() {
super.viewDidLoad()
}
}
With some external help, I was able to identify the source of the 1st problem and rectify them. I was also able to hear necessary advice for the 2nd problem.
Problem 1
The issue was that the exerciseSetsViewController
that I "instantiated via storyboard" was different from the exerciseSetsViewController
that would have been shown from TabbarController
's 2nd tab (Workout Tab). Hence, the workoutVM
was not passed to correct viewcontroller. Hence, below corrected codes needed to be used if I wanted to use either
MyTrainingViewControllerDelegate
and/orExerciseSetsViewController
and loading
the view.The corrected code ensured that the exerciseSetsViewController
that was instantiated was placed as the TabbarController
's 2nd tab.
protocol MyTrainingViewControllerDelegate {
func passWorkoutVM(workoutVM: WorkoutViewModel)
}
class MyTrainingViewController: UIViewController {
var workoutVM: WorkoutViewModel?
var delegate: MyTrainingViewControllerDelegate!
@IBOutlet weak var dayProgramTableView: UITableView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
dayProgramTableView.delegate = self
dayProgramTableView.dataSource = self
}
}
extension MyTrainingViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let tabbar = tabBarController as! MainTabBarController
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let ExerciseSetsViewController = storyBoard.instantiateViewController(withIdentifier: "ExerciseSetsView") as! ExerciseSetsViewController
guard let workoutVM = self.workoutVM else {
return
}
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let exerciseSetsViewController = storyBoard.instantiateViewController(withIdentifier: "ExerciseSetsView") as! ExerciseSetsViewController
guard let nav = tabbar.viewControllers?[1] as? UINavigationController, let exerciseSetsViewController = nav.viewControllers.first as? ExerciseSetsViewController else { return }
print("printing workoutViewModel dayprogram no.of exercise at MyTrainingView \(workoutVM.dayprograms[indexPath.row].dayprogram.dayIntensity)")
// 1.Instantiate view via storyboard Method
exerciseSetsViewController.loadViewIfNeeded()
exerciseSetsViewController.workoutVM = workoutVM
// 2.Delegate Method
self.delegate?.passWorkoutVM(workoutVM: workoutVM)
}
Problem 2
I was advised that my approach to utilize the "tabbar" is literally the same as using "Singleton" because there is one shared source of data where multiple views are accessing. Likewise, my approach to utilize view models that can be accessed via multiple view controller is same as using "global" variable, which has similar repercussion as using "Singletons".
While this approach can be acceptable in certain cases, it is not the "best practice" and I will need to change some of my codes/approach, where each view controller would have their own set of data/view models.