简体   繁体   中英

Access same View Model across multiple tabs in Swift

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

  1. Method 1 - By using Delegate MyTrainingViewControllerDelegate and/or
  2. Method 2 - By instantiating ExerciseSetsViewController 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

  1. Method 1 - By using Delegate MyTrainingViewControllerDelegate and/or
  2. Method 2 - By instantiating ExerciseSetsViewController 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM