Passing data between ViewControllers using Coordinator design pattern (without prepareForSegue)

529 Views Asked by At

My project is structured in MVVM architecture and I'm using the Coordinator design pattern to handle navigation (all programmatically, no Storyboards involved).

It consists of several TableViewControllers with users, albums, photos, posts and comments, each one with its own TableViewController. In each user cell, you can access its albums and posts. Selecting an album or a post, you can see more details, etc...

I'm fetching the data from {JSON} Placeholder API and displaying it on the table views, but can't seem to be able to pass data from the first to the next ones. Since I'm working with a Navigation Controller, and there are no segues, I can't use prepare(for segue:) to setup the next table view before navigating from one view to the other.

I trigger navigation through the functions didTapAlbums(with userId: Int, by name: String) and didTapPosts(with userId: Int, by name: String) which calls the method eventOcurred(with: Event) setup in my Coordinator protocol, that pushes the next TableViewController into the NavigationController.

Here's my Main Coordinator class:

import UIKit

class MainCoordinator: Coordinator {
    
    var navigationController: UINavigationController
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func eventOccurred(with type: Event) {
        switch type {
        case .didTapAlbums:
            var albumsTableViewController: UITableViewController & Coordinating = AlbumTableViewController()
            albumsTableViewController.coordinator = self
            navigationController.pushViewController(albumsTableViewController, animated: true)
        
        case .didTapAlbumCell:
            var photoTableViewController: UITableViewController & Coordinating = PhotoTableViewController()
            photoTableViewController.coordinator = self
            navigationController.pushViewController(photoTableViewController, animated: true)
            
        case .didTapPhotoCell:
            var detailsViewController: UIViewController & Coordinating = DetailsViewController()
            detailsViewController.coordinator = self
            navigationController.pushViewController(detailsViewController, animated: true)
            
        case .didTapPosts:
            var postsTableViewController: UITableViewController & Coordinating = PostTableViewController()
            postsTableViewController.coordinator = self
            navigationController.pushViewController(postsTableViewController, animated: true)
            
        case .didTapPostCell:
            var commentTableViewController: UITableViewController & Coordinating = CommentTableViewController()
            commentTableViewController.coordinator = self
            navigationController.pushViewController(commentTableViewController, animated: true)
            
        
        }
    }
    
    func start() {
        var challengeViewController: UITableViewController & Coordinating = ChallengeViewController()
        challengeViewController.coordinator = self
        navigationController.setViewControllers([challengeViewController], animated: false)
    }
    
    
}

Here's the first TableViewController, which displays the users:

import Alamofire
import UIKit

class ChallengeViewController: UITableViewController, Coordinating {
   
    var coordinator: Coordinator?
        
    let userViewModel = UserViewModel()
    
    var users = [User]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
                
        tableView.dataSource = self
        tableView.register(UserTableViewCell.self, forCellReuseIdentifier: "UserCell")
        tableView.rowHeight = 233
        
        userViewModel.fillUsers { data in
            self.users = data
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }
    }
}

// MARK: - TableView DataSource

extension ChallengeViewController {
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return users.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath) as! UserTableViewCell
        
        let user = users[indexPath.row]
        cell.selectionStyle = .none
        cell.id = user.id

        cell.initialsLabel.text = String(user.name.prefix(2))
        cell.nameLabel.text = user.name
        cell.userNameLabel.text = user.username
        cell.emailLabel.text = user.email
        cell.phoneLabel.text = user.phone
        cell.delegate = self
        cell.backgroundColor = indexPath.row % 2 == 0 ? .white : UIColor(white: 0.667, alpha: 0.2)
        return cell
    }
}

// MARK: - Navigation

extension ChallengeViewController: UserTableViewCellDelegate {
    
    func didTapAlbums(with userId: Int, by name: String) {
        coordinator?.eventOccurred(with: .didTapAlbums)
        
    }
    
    func didTapPosts(with userId: Int, by name: String) {
        coordinator?.eventOccurred(with: .didTapPosts)
    }
}

All I want to do is pass userId and name values to the next Controller.

EDIT: Coordinator and Coordinating protocols

import UIKit

enum Event {
    case didTapAlbums, didTapPosts, didTapAlbumCell, didTapPhotoCell, didTapPostCell
}

protocol Coordinator {
    var navigationController: UINavigationController { get set }
    
    func eventOccurred(with type: Event)
    
    func start()
}

protocol Coordinating {
    var coordinator: Coordinator? { get set }
}
0

There are 0 best solutions below