I tried to make Swift MVC to MVVM but I can't get the data when I switch to the detail page?

57 Views Asked by At

I'm trying to make MVVM from MVC, but I can't transfer the information that I could transfer before, through prepare. What is the reason? How can I fix ?

MainViewController

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showDetail", let detailVC = segue.destination as? DetailViewController, let indexPath = tableView.indexPathForSelectedRow {
            let pokemon = viewModel.pokemons[indexPath.row]
            detailVC.viewModel?.pokemonId = "\(indexPath.row + 1)"
            detailVC.viewModel?.name = pokemon.name
            detailVC.viewModel?.imageURL = viewModel.getPokemonSpritesURL(for: indexPath.row)
        }
    }

DetailViewModel


class DetailViewModel {
    
    // MARK: - Properties
    
    var pokemonId: String
    let apiService: PokeAPIService
    
    var name: String?
    var abilities: [Ability]?
    var imageURL: URL?
    
    // MARK: - Initializers
    
    init(pokemonId: String, apiService: PokeAPIService) {
        self.pokemonId = pokemonId
        self.apiService = apiService
    }
    
    // MARK: - Public Methods
    
    func fetchPokemonDetails(completion: @escaping () -> Void) {
        apiService.getPokemon(by: pokemonId) { [weak self] response in
            guard let self = self else { return }
            self.name = response.name
            self.abilities = response.abilities
            if let urlString = response.sprites?.frontDefault {
                self.imageURL = URL(string: urlString)
            }
            completion()
        }
    }
}

DetailViewController

var viewModel: DetailViewModel?

@IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var abilitiesNameLabel: UILabel!
    @IBOutlet weak var abilitiesNameLabel2: UILabel!
// MARK: - Private Methods
    
     func configureView() {
        viewModel?.fetchPokemonDetails(completion: { [weak self] in
            guard let self = self else { return }
            DispatchQueue.main.async {
                self.nameLabel.text = self.viewModel?.name
                self.abilitiesNameLabel.text = self.viewModel?.abilities?[0].ability?.name
                self.abilitiesNameLabel2.text = self.viewModel?.abilities?[1].ability?.name
                if let imageURL = self.viewModel?.imageURL {
                    self.loadImage(from: imageURL)
                }
            }
        })
    }
    
     func loadImage(from url: URL) {
        URLSession.shared.dataTask(with: url) { [weak self] (data, response, error) in
            guard let self = self, let data = data else { return }
            DispatchQueue.main.async {
                self.imageView.image = UIImage(data: data)
            }
        }.resume()
    }

When I switch back to MVC I don't have any problems. I can't find where I'm doing wrong It seems like I'm missing something small but I can't figure it out.

2

There are 2 best solutions below

1
Kstin On BEST ANSWER

Your problem is here

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "showDetail", let detailVC = segue.destination as? DetailViewController, let indexPath = tableView.indexPathForSelectedRow {
        let pokemon = viewModel.pokemons[indexPath.row]
        detailVC.viewModel?.pokemonId = "\(indexPath.row + 1)"
        detailVC.viewModel?.name = pokemon.name
        detailVC.viewModel?.imageURL = viewModel.getPokemonSpritesURL(for: indexPath.row)
    }
}

at this step detailVC.viewModel is nil and setting to VM some properties doesn't have any result. So in this method you need to create instance of DetailViewModel with all required data and set it to detailVC like this:

detailVC.viewModel = DetailViewModel(id: id, name: name, url: url)
1
Md Fahim Faez Abir On

The reason it's not working is because your MVVM structure may not be correct. In MVVM, you don't need to use prepare method. Instead, you can use the Observable or Combine pattern. These patterns notify the View Controller when there is a change in the value. In the View Controller, you can use a Binder to bind the ViewModel's output to the View's input. This way, the View is always up to date with the latest ViewModel data.

You can learn MVVM(Observable pattern) from this link:- [1]: https://www.youtube.com/watch?v=sLHVxnRS75w

You can learn MVVM(Combine) from this link:- https://www.youtube.com/watch?v=30LapcVtyBQ

I could have modified your code to use the MVVM design pattern, but I think it's important for you to understand how it works. That's why I'm providing you with these two tutorial links.

Thank You!