So i am trying to refactor an existing project from MMVM and to add coordinator. i have the following classes:
protocol Coordinator {
func start()
}
class BaseCoordinator: Coordinator {
private var presenter: UINavigationController
private var genreViewController: ViewController?
private var viewModel = GenreViewModel()
init(presenter: UINavigationController) {
self.presenter = presenter
}
func start() {
let genreViewController = ViewController()
genreViewController.viewModel = viewModel
self.genreViewController = genreViewController
presenter.pushViewController(genreViewController, animated: true)
}
}
class AppCoordinator: Coordinator {
private let window: UIWindow
private let rootViewController: UINavigationController
private var genereListCoordinator: BaseCoordinator?
init(window: UIWindow) {
self.window = window
rootViewController = UINavigationController()
rootViewController.navigationBar.prefersLargeTitles = true
genereListCoordinator = BaseCoordinator(presenter: rootViewController)
}
func start() {
window.rootViewController = rootViewController
genereListCoordinator?.start()
window.makeKeyAndVisible()
}
}
In appDelegate i do as below:
var window: UIWindow?
var applicationCoordinator: AppCoordinator?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let window = UIWindow(frame: UIScreen.main.bounds)
let appCordinator = AppCoordinator(window: window)
self.window = window
self.applicationCoordinator = appCordinator
appCordinator.start()
return true
}
VC is:
class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
var viewModel: GenreViewModel!
override func viewDidLoad() {
super.viewDidLoad()
if let flowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.itemSize = CGSize(width: self.collectionView.bounds.width, height: 50)
}
self.collectionView.delegate = self
self.collectionView.dataSource = self
collectionView.backgroundView = UIImageView(image: UIImage(named: "131249-dark-grey-low-poly-abstract-background-design-vector.jpg"))
self.viewModel.delegate = self
self.getData()
}
func getData() {
MBProgressHUD.showAdded(to: self.view, animated: true)
viewModel.getGenres()
}
}
extension ViewController: GenreViewModelDelegate { func didfinish(succsess: Bool) { MBProgressHUD.hide(for: self.view, animated: true) if succsess { self.collectionView.reloadData() } else { let action = UIAlertAction(title: "Try again", style: .default, handler: { (action) in self.getData() }) Alerts.showAlert(vc: self, action: action) } } }
extension ViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: 100, height: 100) } }
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewModel.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: GenreCollectionViewCell.reuseIdentifier, for: indexPath) as? GenreCollectionViewCell else {
return UICollectionViewCell()
}
let cellViewModel = viewModel.cellViewModel(index: indexPath.row)
cell.viewModel = cellViewModel
return cell
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cellViewModel = viewModel.cellViewModel(index: indexPath.row)
viewModel.didSelectGenre(index: (cellViewModel?.id)!)
}
}
VM is :
protocol GenreViewModelDelegate: class {
func didfinish(succsess: Bool)
}
protocol GenreListCoordinatorDelegate: class {
func movieListDidGenre(id: String)
}
class GenreViewModel {
weak var coordinatorDelegate: GenreListCoordinatorDelegate?
var networking = Networking()
var genresModels = [Genres]()
weak var delegate: GenreViewModelDelegate?
func getGenres() {
self.networking.preformNetwokTask(endPoint: TheMoviedbApi.genre, type: Genre.self, success: { [weak self] (response) in
print(response)
if let genres = response.genres {
self?.genresModels = genres
self?.delegate?.didfinish(succsess: true)
} else {
self?.delegate?.didfinish(succsess: false)
}
}) {
self.delegate?.didfinish(succsess: false)
}
}
var count: Int {
return genresModels.count
}
public func cellViewModel(index: Int) -> GenreCollectionViewCellModel? {
let genreCollectionViewCellModel = GenreCollectionViewCellModel(genre: genresModels[index])
return genreCollectionViewCellModel
}
public func didSelectGenre(index: String) {
coordinatorDelegate?.movieListDidGenre(id: index)
}
}
The problem is that when i am trying to inject the viewModel to the ViewController and the push it in the start function it wont work-when the viewDidLoad invoked the viewModel in the VC is nil.
With the same code, I managed to make it work, the
viewModel
property is populated on my side.Often the issues with Coordinator pattern is to retaining the navigation stack which would not fire
viewDidLoad
or don't display screen at all for instance. In your case, if onlyviewModel
is missing, I believe it comes from aViewController
constructor issue or an override.I can see an
@IBOutlet
in yourViewController
which makes me think you are using storyboard / xib file. However, theBaseCoordinator
only use ainit()
, could it be the issue? If using Storyboard, you should try withinstantiateViewController(...)
.On another note, you should look into retaining all stack of coordinators to handle a full navigation and avoid retaining
viewModel
or other properties within coordinator when needed. IfUINavigationController
retain child UIViewController, you wouldn't need to as well.