ios - 使用 MVP 模式 iOS 将数据传递给另一个控制器
问题描述
我正在使用 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属性应该是公共的还是私有的?
解决方案
在 Objective-C 中,您可以拥有一个传递模型的视图。我们可以在视图控制器的头文件中前向声明一个模型:
@class HotelTemplate;
在 .m 文件中,我会确保不要"#import HotelTemplate.h"
. 这样,模型保持不透明。你可以传递它,但你不能看里面。
我不知道有任何方法可以在 Swift 中强制执行此操作。所以让我按照你的例子,通过下一个演示者,而不是下一个模型。我们所需要的只是一种在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)
}
}
在每一步,它都服从于演示者。Presenter 与 View 的链接较弱,但只能通过协议。它不知道 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
}
}
在实践中,您会向表格视图单元格添加更多内容。我只是在这个例子中使用了一个普通的单元格和它的文本标签。
最后,我们来到推送的视图控制器。
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()
}
}
我假设我们将立即显示我们拥有的摘要信息,但还有更多细节来自 Web 服务。这是由这个 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)
,以便只有 setter 是私有的。
推荐阅读
- html - 将两个 p 元素并排放置,宽度为 50%
- c# - 列表
要字符串的项目 - java - 如何让我的角度 Spring 的 UI 更改显示
- javascript - PayPal REST SDK:删除送货地址和付款授权,使其进入待处理状态
- docker - PyCharm 无法在本地 docker-machine 中使用解释器
- twitter-bootstrap - 为什么没有类限制宽度的div
- python - Scrapy:ImportError:没有名为 scrapy_proxies 的模块
- cloud - 寻找与 jira 集成进行外部通信的工具
- javascript - 无法启用表单元素
- c# - 来自 label.text 奇怪行为的字符串