我正处于学习新的swift的阶段,我将我的应用程序设计为mvc设计模式。我经历了一次学习mvvm: d的冒险。
有些部分我还是不明白。我了解到我需要在ViewModel部分不使用UIKit的情况下转移,但我不知道如何转移它。我必须找到通往那里的路。我有10个视图控制器页面,我想让他们都根据mvvm。
我试图转换我的设计从MVC到MVVM,但我得到这个错误我怎么能解决它?
BreedsViewController
import UIKit
import ProgressHUD
protocol BreedsViewControllerInterface: AnyObject {
func prepareCollectionView()
}
final class BreedsViewController: UIViewController {
@IBOutlet weak var categoryCollectionView: UICollectionView!
// main storyboard collection View adding (dataSource, delegate)
@IBOutlet weak var popularCollectionView: UICollectionView!
// main storyboard collection View adding (dataSource, delegate)
@IBOutlet weak var specialsCollectionView: UICollectionView!
// main storyboard collection View adding (dataSource, delegate)
private lazy var viewModel = BreedsVM()
// data, move mvvm
var categories: [DogCategory] = []
var populars: [Breed] = []
var downCategories:[Breed] = []
override func viewDidLoad() {
super.viewDidLoad()
viewModel.view = self
viewModel.viewDidLoad()
}
private func registerCell() {
categoryCollectionView.register(UINib(nibName: CategoryCollectionViewCell.identifier, bundle: nil), forCellWithReuseIdentifier: CategoryCollectionViewCell.identifier)
popularCollectionView.register(UINib(nibName: DogPortraitCollectionViewCell.identifier, bundle: nil), forCellWithReuseIdentifier: DogPortraitCollectionViewCell.identifier)
specialsCollectionView.register(UINib(nibName: DogLandscapeCollectionViewCell.identifier, bundle: nil), forCellWithReuseIdentifier: DogLandscapeCollectionViewCell.identifier)
}
}
extension BreedsViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch collectionView {
case categoryCollectionView:
return categories.count
case popularCollectionView:
return populars.count
case specialsCollectionView:
return downCategories.count
default: return 0
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch collectionView {
case categoryCollectionView:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CategoryCollectionViewCell.identifier, for: indexPath) as! CategoryCollectionViewCell
cell.setup(category: categories[indexPath.row])
return cell
case popularCollectionView:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DogPortraitCollectionViewCell.identifier, for: indexPath) as! DogPortraitCollectionViewCell
cell.setup(breed: populars[indexPath.row])
return cell
case specialsCollectionView:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DogLandscapeCollectionViewCell.identifier, for: indexPath) as! DogLandscapeCollectionViewCell
cell.setup(breed: downCategories[indexPath.row])
return cell
default: return UICollectionViewCell()
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == categoryCollectionView {
let controller = ListDogsViewController.instantiate()
controller.category = categories[indexPath.row]
navigationController?.pushViewController(controller, animated: true)
} else {
let controller = FavoriteDetailViewController.instantiate()
controller.breed = collectionView == popularCollectionView ? populars[indexPath.row] : downCategories[indexPath.row]
navigationController?.pushViewController(controller, animated: true)
}
}
}
extension BreedsViewController: BreedsViewControllerInterface {
func prepareCollectionView() {
registerCell()
ProgressHUD.show()
NetworkService.shared.fetchAllCategories { [weak self] (result) in
switch result {
case.success(let allBreed):
ProgressHUD.dismiss()
self?.categories = allBreed.categories ?? []
self?.populars = allBreed.populars ?? []
self?.downCategories = allBreed.downCategories ?? []
self?.categoryCollectionView.reloadData()
self?.popularCollectionView.reloadData()
self?.specialsCollectionView.reloadData()
case.failure(let error):
ProgressHUD.showError(error.localizedDescription)
}
}
}
}
BreedsVM
import Foundation
protocol BreedsVMInterface {
var view: BreedsViewControllerInterface? { get set }
func viewDidLoad()
func didSelectItemAt(indexPath: IndexPath)
}
final class BreedsVM {
weak var view: BreedsViewControllerInterface?
}
extension BreedsVM: BreedsVMInterface {
func didSelectItemAt(indexPath: IndexPath) {
}
func viewDidLoad() {
view?.prepareCollectionView()
}
}
例如,我想根据Mvvm应用didselectItemAt。当我想这样做时,我得到以下错误。我怎么解它?
改变BreedsViewController
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
viewModel.didSelectItemAt(indexPath: indexPath)
}
改变BreedsVM
import Foundation
protocol BreedsVMInterface {
var view: BreedsViewControllerInterface? { get set }
func viewDidLoad()
func didSelectItemAt(indexPath: IndexPath)
}
final class BreedsVM {
weak var view: BreedsViewControllerInterface?
var categories: [DogCategory] = []
var populars: [Breed] = []
var downCategories:[Breed] = []
}
extension BreedsVM: BreedsVMInterface {
func didSelectItemAt(indexPath: IndexPath) {
if collectionView == categoryCollectionView {
let controller = ListDogsViewController.instantiate()
controller.category = categories[indexPath.row]
navigationController?.pushViewController(controller, animated: true)
} else {
let controller = FavoriteDetailViewController.instantiate()
controller.breed = collectionView == popularCollectionView ? populars[indexPath.row] : downCategories[indexPath.row]
navigationController?.pushViewController(controller, animated: true)
}
}
func viewDidLoad() {
view?.prepareCollectionView()
}
}
BreedsVM的警告和错误
在作用域中找不到'categoryCollectionView'在作用域中找不到'collectionView'在作用域中找不到'popularCollectionView'
当我们从MVC转移到任何其他架构时,我们这样做是为了实现业务逻辑和UI逻辑的分离,所以例如在MVVM中,ViewModel不应该知道任何关于UI的事情,而且ViewController应该只是做UI的东西(改变颜色,显示和隐藏UI元素,…)也在MVVM,连接应该从一边的ViewController, ViewController应该有一个实例从ViewModel,但ViewModel应该有任何参考从ViewController,但我们如何实现UI的改变后处理一些逻辑?这可以通过多种方式来实现,例如:Combine或RxSwift甚至闭包,但为了简单起见,我们可以从使用闭包开始绑定,让我们举一个例子:
//视图模型
class BreedsViewModel {
// MARK: - Closures
var fetchCategoriesSucceeded: ( (_ categories: [DogCategory], _ populars: [Breed], _ downCategories: [Breed]) -> Void )?
var fetchCategoriesFailed: ( (_ errorMessage: String) -> Void )?
// MARK: - Fetch Categories API
func fetchCategories(){
// Also this should be injected to the ViewModel instead of using it as a singleton, read more about dependency injection
NetworkService.shared.fetchAllCategories { [weak self] (result) in
switch result {
case.success(let allBreed):
self?.fetchCategoriesSucceeded?(allBreed.categories, allBreed.populars, allBreed.downCategories)
case.failure(let error):
self?.fetchCategoriesFailed?(error.localizedDescription)
}
}
}
}
//ViewController
class BreedsViewController: UIViewController {
var viewModel = BreedsViewModel() // This should be injected to the view controller
private var categories: [DogCategory] = []
private var populars: [Breed] = []
private var downCategories:[Breed] = []
override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
fetchCategories()
}
private func fetchCategories(){
// ProgressHUD.show()
viewModel.fetchCategories()
}
private func bindViewModel() {
viewModel.fetchCategoriesSucceeded = { [weak self] categories, populars, downCategories in
// ProgressHUD.dismiss()
self?.categories = categories
self?.populars = populars
self?.downCategories = downCategories
// collectionView.reloadData()
}
viewModel.fetchCategoriesFailed = { [weak self] errorMessage in
// ProgressHUD.showError(errorMessage)
}
}
}
现在你可以看到,ViewModel不知道任何关于UI的事情,只是从API获取数据,然后通过闭包通知ViewController,当ViewController通知时,它应该更新UI。
我也可以看到你想要实现的与MVP更相关,有一个演示者和一个视图控制器,演示者将有一个来自视图控制器的弱引用并通过委托更新视图控制器
//主持人
protocol BreedsPresenterDelegate: AnyObject {
func fetchCategoriesSucceeded(_ categories: [DogCategory], _ populars: [Breed], _ downCategories: [Breed])
func fetchCategoriesFailed(_ errorMessage: String)
}
class BreedsPresenter {
weak var delegate: BreedsPresenterDelegate?
func fetchCategories(){
NetworkService.shared.fetchAllCategories { [weak self] (result) in
switch result {
case.success(let allBreed):
self?.delegate?.fetchCategoriesSucceeded(allBreed.categories, allBreed.populars, allBreed.downCategories)
case.failure(let error):
self?.delegate?.fetchCategoriesFailed(error.localizedDescription)
}
}
}
}
//ViewControllerclass BreedsViewController: UIViewController {
var presenter = BreedsPresenter() // This should be injected to the view controller
private var categories: [DogCategory] = []
private var populars: [Breed] = []
private var downCategories:[Breed] = []
override func viewDidLoad() {
super.viewDidLoad()
presenter.delegate = self
fetchCategories()
}
private func fetchCategories(){
// ProgressHUD.show()
presenter.fetchCategories()
}
}
extension BreedsViewController: BreedsPresenterDelegate {
func fetchCategoriesSucceeded(_ categories: [DogCategory], _ populars: [Breed], _ downCategories: [Breed]) {
// ProgressHUD.dismiss()
self.categories = categories
self.populars = populars
self.downCategories = downCategories
// collectionView.reloadData()
}
func fetchCategoriesFailed(_ errorMessage: String) {
// ProgressHUD.showError(errorMessage)
}
}
我希望这对你有帮助。