В Objective-C вы можете иметь представление, передающее модель.Мы могли бы заранее объявить модель в заголовочном файле контроллера вида:
@class HotelTemplate;
В файле .m я бы не стал указывать "#import HotelTemplate.h"
.Таким образом, модель оставалась непрозрачной.Вы можете передать его, но не можете заглянуть внутрь.
Я не знаю ни одного способа применить это в Swift.Итак, позвольте мне последовать вашему примеру и передать следующего докладчика вместо следующей модели.Все, что нам нужно, это способ установить View для каждого докладчика в viewDidLoad()
.Чтобы предотвратить сохранение циклов, это будет слабое свойство.
Во-первых, вот контроллер представления списка.Я сделал его UITableViewController.
final class ListHotelViewController: UITableViewController {
private var presenter = ListHotelPresenter()
override func viewDidLoad() {
super.viewDidLoad()
presenter.setView(self)
presenter.loadHotelData()
}
}
Докладчик перезвонит через протокол:
protocol ListHotelView: class {
func redraw()
func showDetails(nextPresenter: HotelDetailsPresenter)
}
extension ListHotelViewController: ListHotelView {
func redraw() {
tableView.reloadData()
}
func showDetails(nextPresenter: HotelDetailsPresenter) {
let vc = HotelDetailsViewController.init(with: nextPresenter)
navigationController?.pushViewController(vc, animated: true)
}
}
Вот источник данных и делегат табличного представления:
extension ListHotelViewController /* UITableViewDataSource */ {
public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return presenter.hotelCount
}
public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Hotel", for: indexPath) as! HotelTableViewCell
presenter.configure(cell: cell, row: indexPath.row)
return cell
}
}
extension ListHotelViewController /* UITableViewDelegate */ {
public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
presenter.showDetails(row: indexPath.row)
}
}
На каждом шаге он подчиняется докладчику.Ведущий имеет слабую связь с представлением, но только через протокол.Он понятия не имеет, что View является ListHotelViewController.Мы должны иметь возможность реализовать представление с кучей операторов print(_)
вместо этого для интерфейса на основе терминала.
final class ListHotelPresenter {
private weak var view: ListHotelView?
private var model: [HotelTemplate] = [] {
didSet {
view?.redraw()
}
}
var hotelCount: Int {
return model.count
}
func setView(_ view: ListHotelView) {
self.view = view
}
func loadHotelData() {
// Network request to load data into model. Let's pretend with dummy data:
let hilton = HotelTemplate(id: "hilton", name: "Hilton", icon: "H")
let radisson = HotelTemplate(id: "radisson", name: "Radisson", icon: "R")
model = [hilton, radisson]
}
func configure(cell: HotelCell, row: Int) {
let hotel = model[row]
cell.show(name: hotel.name, icon: hotel.icon)
}
func showDetails(row: Int) {
let nextPresenter = HotelDetailsPresenter(summaryModel: model[row])
view?.showDetails(nextPresenter: nextPresenter)
}
}
В configure(cell:row:)
Presenter обращается к данной ячейке.Обратите внимание, что ячейка также является протоколом.С MVP я действительно пытаюсь представить, как бы я использовал его для создания интерфейса на основе терминала.Вот ячейка:
protocol HotelCell: class {
func show(name: String, icon: String)
}
final class HotelTableViewCell: UITableViewCell {}
extension HotelTableViewCell: HotelCell {
func show(name: String, icon: String) {
textLabel?.text = name
// Do something to show icon
}
}
На практике вы бы добавили больше в ячейку табличного представления.Я просто использовал простую ячейку и ее текстовую метку для этого примера.
Наконец, мы подошли к контроллеру push-представления.
final class HotelDetailsViewController: UIViewController {
private var presenter: HotelDetailsPresenter!
@IBOutlet private var textLabel: UILabel!
static func `init`(with presenter: HotelDetailsPresenter) -> HotelDetailsViewController {
let vc = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "HotelDetailsViewController")
as! HotelDetailsViewController
vc.presenter = presenter
return vc
}
override func viewDidLoad() {
super.viewDidLoad()
presenter.setView(self)
presenter.show()
}
}
Я предполагаю, что мы немедленнопоказать сводную информацию, которую мы имеем, но это еще не все детали веб-службы.Это делает этот Presenter.
struct HotelDetails {
let location: String
// more details…
}
final class HotelDetailsPresenter {
private weak var view: HotelDetailsView?
private let summaryModel: HotelTemplate
private var detailsModel: HotelDetails? {
didSet {
guard let detailsModel = detailsModel else { return }
view?.showDetails(location: detailsModel.location)
}
}
init(summaryModel: HotelTemplate) {
self.summaryModel = summaryModel
}
func setView(_ view: HotelDetailsView) {
self.view = view
}
func show() {
view?.show(name: summaryModel.name, icon: summaryModel.icon)
// Network request to load data into detailsModel
}
}
Как обычно, Presenter сообщает View, что делать через протокол:
protocol HotelDetailsView: class {
func show(name: String, icon: String)
func showDetails(location: String)
}
extension HotelDetailsViewController: HotelDetailsView {
func show(name: String, icon: String) {
textLabel?.text = name
// Do something to show icon
}
func showDetails(location: String) {
// Show other hotel details we loaded
}
}
Как видите, свойства являются частными.Для поддержки модульного тестирования нам может потребоваться ослабить его, используя private(set)
, чтобы только сеттеры были частными.