Reputation: 2209
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.
Upvotes: 0
Views: 426
Reputation: 1434
With the same code, I managed to make it work, the viewModel
property is populated on my side.
// Coordinator
protocol Coordinator {
func start()
}
class BaseCoordinator: Coordinator {
private var presenter: UINavigationController
private var genreViewController: ViewController?
private var viewModel = ViewModel()
init(presenter: UINavigationController) {
self.presenter = presenter
}
func start() {
let genreViewController = ViewController()
genreViewController.viewModel = viewModel
self.genreViewController = genreViewController
presenter.pushViewController(genreViewController, animated: true)
}
}
// ViewController + ViewModel
struct ViewModel { }
class ViewController: UIViewController {
var viewModel: ViewModel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.backgroundColor = .white
}
}
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
window.makeKeyAndVisible()
genereListCoordinator?.start()
}
}
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
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()
let appCordinator = AppCoordinator(window: window)
self.window = window
self.applicationCoordinator = appCordinator
appCordinator.start()
return true
}
}
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 only viewModel
is missing, I believe it comes from a ViewController
constructor issue or an override.
I can see an @IBOutlet
in your ViewController
which makes me think you are using storyboard / xib file. However, the BaseCoordinator
only use a init()
, could it be the issue? If using Storyboard, you should try with instantiateViewController(...)
.
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. If UINavigationController
retain child UIViewController, you wouldn't need to as well.
Upvotes: 0