Передача данных на другой контроллер с использованием шаблона MVP iOS - PullRequest
0 голосов
/ 30 января 2019

Я использую шаблон проектирования MVP.У меня есть два способа передачи данных в другой контроллер представления, о котором я расскажу ниже.Я не знаю, какой из них является правильным и не нарушает шаблон MVP.Я знаю, что это очень большой вопрос, но он действительно очень важен.

1) Используя init with presenter, ниже я создаю контроллер представления, передавая презентатора, который необходим контроллеру представления.

struct HotelTemplate {
    var id: String
    var name: String
    var icon: String
}

class ListHotelPresenter: NSObject {

    private var data = [HotelTemplate]()

    func getPresenter(_ index: Int) -> HotelDetailsPresenter {
        let presenter = HotelDetailsPresenter(id: data[index].id, name: data[index].name, icon: data[index].icon)
        return presenter
    }
}

// InitialViewController
class ListHotelViewController: UIViewController {

    class func `init`(with presenter: ListHotelPresenter) -> ListHotelViewController {
        let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ListHotelViewController") as! ListHotelViewController
        vc.presenter = presenter
        return vc
    }

    var presenter: ListHotelPresenter!

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let detailPresenter = presenter.getPresenter(indexPath.row)
        let vc = HotelDetailsViewController.init(with: detailPresenter)
        self.navigationController?.pushViewController(vc, animated: true)
    }
}

// ViewController that will be push
class HotelDetailsViewController: UIViewController {

    class func `init`(with presenter: HotelDetailsPresenter) -> HotelDetailsViewController {
        let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "HotelDetailsViewController") as! HotelDetailsViewController
        return vc
    }

    var presenter: HotelDetailsPresenter!

    override func viewDidLoad() {
        super.viewDidLoad()

        presenter.loadHoteData()
    }
}

class HotelDetailsPresenter: NSObject {
    var hotelId: String
    var hotelName: String
    var hotelIcon: String

    init(id: String, name: String, icon: String) {
        self.hotelId = id
        self.hotelName = name
        self.hotelIcon = icon
    }

    func loadHoteData() {
        // Load hotel data.
        // Alamofire.request ..................
    }
}

2) Отправив id, name, icon, а затем инициализировав докладчика в viewDidLoad()

struct HotelTemplate {
    var id: String
    var name: String
    var icon: String
}

class ListHotelPresenter: NSObject {

    private var data = [HotelTemplate]()

    func getHotelName(_ index: Int) -> String {
        return data[index].name
    }

    func getHotelIcon(_ index: Int) -> String {
        return data[index].icon
    }

    func getHotelId(_ index: Int) -> String {
        return data[index].id
    }
}

class ListHotelViewController: UIViewController {

    var presenter: ListHotelPresenter!

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "HotelDetailsViewController") as! HotelDetailsViewController
        vc.id = presenter.getHotelId(indexPath.row)
        vc.name = presenter.getHotelName(indexPath.row)
        vc.icon = presenter.getHotelIcon(indexPath.row)
        self.navigationController?.pushViewController(vc, animated: true)
    }
}

class HotelDetailsViewController: UIViewController {

    var presenter: HotelDetailsPresenter!
    var id = ""
    var name = ""
    var icon = ""

    override func viewDidLoad() {
        super.viewDidLoad()
        presenter = HotelDetailsPresenter(id: id, name: name, icon: icon)
        presenter.loadHoteData()
    }
}

class HotelDetailsPresenter: NSObject {
    var hotelId: String
    var hotelName: String
    var hotelIcon: String

    init(id: String, name: String, icon: String) {
        self.hotelId = id
        self.hotelName = name
        self.hotelIcon = icon
    }

    func loadHoteData() {
        // Load hotel data.
        // Alamofire.request ..................
    }
}

Итак, ниже мои вопросы:

1) Какой из них правильный?(Я чувствую, что первый метод действительно очень чистый, но мой старший сказал мне, что он нарушает шаблон MVP. Я не знаю как.)

2) Должно ли свойство Presenter контроллера быть открытым или закрытым

Ответы [ 2 ]

0 голосов
/ 03 февраля 2019

В 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), чтобы только сеттеры были частными.

0 голосов
/ 30 января 2019

Привет, согласно указаниям Apple Model-View-Controller оба в порядке, но второй более читабелен и прост для понимания первого с некоторыми изменениями, пожалуйста, проверьте код.

class ListHotelViewController: UIViewController{
private var presenter: ListHotelPresenter!

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "HotelDetailsViewController") as! HotelDetailsViewController
    vc.objHotelTemplate = presenter.getHotelTemplate(indexPath.row)
    self.navigationController?.pushViewController(vc, animated: true)
}
}
class HotelDetailsViewController: UIViewController {

private var presenter: HotelDetailsPresenter!
var objHotelTemplate:HotelTemplate!

override func viewDidLoad() {
    super.viewDidLoad()
    presenter = HotelDetailsPresenter(id: objHotelTemplate.id, name: objHotelTemplate.name, icon: objHotelTemplate.icon)
    presenter.loadHoteData()
}

}

...