Edu
Edu

Reputation: 470

How to pass data from ViewModel Layer to View Layer

I don't know how to pass data from my viewModel to my view and finally show the data in the view, my view model class is:

class MainViewModel {

    let sessionController: SessionController
    weak var mainViewCoordinator: MainViewCoordinator?
    public var fakeUsers: [User]?

    init(sessionController: SessionController = SessionController()) {
        self.sessionController = sessionController
    }

    func viewDidLoad() {
        Service.instance.execute(resource: User.fake) { (result) in
            print("\n result \(result)\n")
            self.fakeUsers = result.value
        }
    }
}

I'm using Unbox Swift JSON decoder, and the printed result is is and array of User objects something like this:

([
  User(id: "5851ac2615801e2348e4ea07", 
       birthDate: "2016-09-17T07:22:09.985Z", 
       msisdn: "912 065 979", 
       email: "[email protected]", 
       profileImageUrl: "https://s3.amazonaws.com/...", 
       repPoints: 41607, 
       created: "2016-12-14T20:31:34.185Z", 
       displayName: "Victoria Escobedo"), 
  User(id: "5851ac2615801e2348e4ea09", 
       birthDate: "2016-05-06T11:38:23.678Z", 
       msisdn: "958842030", 
       email: "[email protected]", 
       profileImageUrl: "https://s3.amazonaws.com/...", 
       repPoints: 71408, 
       created: "2016-12-14T20:31:34.198Z", 
       displayName: "Gonzalo Rascón"), 
   User(id: "5851ac2615801e2348e4ea08", 
        birthDate: "2016-05-29T18:12:32.423Z", 
        msisdn: "905534639", 
        email: "[email protected]", 
        profileImageUrl: "https://s3.amazonaws.com/...", 
        repPoints: 24164, 
        created: "2016-12-14T20:31:34.195Z", 
        displayName: "Ramiro Dueñas"),
   ...
])

And I would like to pass the result to the View Layer (MainViewController) to show each User in table view cells:

class MainViewController: UIViewController, UITableViewDataSource {

@IBOutlet var tableview:UITableView!
var viewModel: MainViewModel = MainViewModel()

override func viewDidLoad() {
    super.viewDidLoad()
    viewModel.viewDidLoad()
    self.tableview?.reloadData()
    self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Action", style: .plain, target: self, action: #selector(action))
}

func action() {
    viewModel.userDidSelectItem(identifier: "xxxx")
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return viewModel.fakeUsers?.count ?? 0
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "fakeUserObjectCell") as! MainTableViewCell
    cell.fakeUserObject = viewModel.fakeUsers?[(indexPath as NSIndexPath).row]
    return cell
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "detailSegue" {
        let detailMainViewController = segue.destination as! DetailMainViewController
        if let indexPath = tableview.indexPath(for: sender as! MainTableViewCell) {
            detailMainViewController.id = viewModel.fakeUsers?[(indexPath as NSIndexPath).row].id
        }
    }
}

}

I know I have to implement self.tableview?.reloadData() to show the fakeusers but I don't know how to pass the data and finally show it.

Upvotes: 0

Views: 98

Answers (2)

Zac Kwan
Zac Kwan

Reputation: 5747

The following Service.instance.execute() in viewModel.viewDidLoad() is a async call. It have not completed the fetching and you have call self.tableview?.reloadData() Therefore this likely resulted in 0 rows.

I would suggest to change ViewModel viewDidLoad() to a completion call

func viewDidLoad() {
    Service.instance.execute(resource: User.fake) { (result) in
        print("\n result \(result)\n")
        self.fakeUsers = result.value
    }
}

Like this:

func loadUsers(completion: () -> ()) {
    Service.instance.execute(resource: User.fake) { (result) in
        print("\n result \(result)\n")
        self.fakeUsers = result.value
        completion()
    }
}

Therefore in the MainViewController, viewDidLoad, you can change the call to only reloadData() when is ready.

override func viewDidLoad() {
    super.viewDidLoad()
    viewModel.loadUsers {
        self.tableview?.reloadData()
    }
    self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Action", style: .plain, target: self, action: #selector(action))
}

Upvotes: 1

ystack
ystack

Reputation: 1805

  • Declare a public var public var fakeUsers: [User]? in MainViewModel
  • Update fakeUsers in MainViewModel's viewDidLoad()
  • No need of a fakeUsers var in MainViewController
  • numberOfRowsInSection in MainViewController now returns the count of fakeUsers set by MainViewModel like:


func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {   
    return viewModel.fakeUsers?.count ?? 0
}

Similarly, for cellForRowAt indexPath:

EDIT:

The Service.instance.execute API call won't return immediately, but when it does, the data array should be updated with new content & the table requested to be refreshed.
But now the problem is that we cannot have table access in MainViewModel.
So, when the Service fetch completes, we can notify MainViewController to perform a table.reloadData()
I hope you can come up with the code for this bit, you'll need to start here:

class MainViewModel {
    ...

    func viewDidLoad() {
        Service.instance.execute(resource: User.fake) { (result) in
            print("\n result \(result)\n")
            self.fakeUsers = result.value
            // Edu, the table refresh notification is to be fired from here
        }
    }
}

Upvotes: 0

Related Questions