我有一个包含表视图的视图控制器,所以我想问一下我应该把表视图数据源和委托放在哪里,如果它是外部对象,或者如果我们说VIPER,我可以在视图控制器中写它图案。
通常使用模式我这样做:
在viewDidLoad中,我从演示者那里请求一些流程,如self.presenter.showSongs()
Presenter包含交互器,在showSongs方法中,我从交互器请求一些数据,如:self.interactor.loadSongs()
当歌曲准备好传回视图控制器时,我再次使用演示者来确定如何在视图控制器中显示这些数据。但我的问题是如何处理表视图的数据源?
答案 0 :(得分:18)
首先,您的View不应该询问Presenter的数据 - 它违反了VIPER架构。
视图是被动的。它等待Presenter给它显示内容;它永远不会要求Presenter提供数据。
至于你的问题: 最好在Presenter中保持当前视图状态,包括所有数据。因为它基于州提供VIPER部分之间的通信。
但另一方面,Presenter不应该对UIKit有所了解,因此UITableViewDataSource和UITableViewDelegate应该是View层的一部分。
为了使ViewController保持良好状态并以“SOLID”方式执行,最好将DataSource和Delegate保存在单独的文件中。但这些部分仍然应该知道主持人询问数据。所以我更喜欢在Extension of ViewController中做到这一点
所有模块应该看起来像这样:
查看强>
ViewController.h
extern NSString * const TableViewCellIdentifier;
@interface ViewController
@end
ViewController.m
NSString * const TableViewCellIdentifier = @"CellIdentifier";
@implemntation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.presenter setupView];
}
- (void)refreshSongs {
[self.tableView reloadData];
}
@end
的ViewController + TableViewDataSource.h
@interface ViewController (TableViewDataSource) <UITableViewDataSource>
@end
的ViewController + TableViewDataSource.m
@implementation ItemsListViewController (TableViewDataSource)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.presenter songsCount];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Song *song = [self.presenter songAtIndex:[indexPath.row]];
// Configure cell
return cell;
}
@end
的ViewController + TableViewDelegate.h
@interface ViewController (TableViewDelegate) <UITableViewDelegate>
@end
的ViewController + TableViewDelegate.m
@implementation ItemsListViewController (TableViewDelegate)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Song *song = [self.presenter songAtIndex:[indexPath.row]];
[self.presenter didSelectItemAtIndex:indexPath.row];
}
@end
<强>演示强>
Presenter.m
@interface Presenter()
@property(nonatomic,strong)NSArray *songs;
@end
@implementation Presenter
- (void)setupView {
[self.interactor getSongs];
}
- (NSUInteger)songsCount {
return [self.songs count];
}
- (Song *)songAtIndex:(NSInteger)index {
return self.songs[index];
}
- (void)didLoadSongs:(NSArray *)songs {
self.songs = songs;
[self.userInterface refreshSongs];
}
@end
<强>交互器强>
Interactor.m
@implementation Interactor
- (void)getSongs {
[self.service getSongsWithCompletionHandler:^(NSArray *songs) {
[self.presenter didLoadSongs:songs];
}];
}
@end
答案 1 :(得分:8)
Swift 3.1 中的示例,可能对某人有用:
查看强>
class SongListModuleView: UIViewController {
// MARK: - IBOutlets
@IBOutlet weak var tableView: UITableView!
// MARK: - Properties
var presenter: SongListModulePresenterProtocol?
// MARK: - Methods
override func awakeFromNib() {
super.awakeFromNib()
SongListModuleWireFrame.configure(self)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
presenter?.viewWillAppear()
}
}
extension SongListModuleView: SongListModuleViewProtocol {
func reloadData() {
tableView.reloadData()
}
}
extension SongListModuleView: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return presenter?.songsCount ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "SongCell", for: indexPath) as? SongCell, let song = presenter?.song(atIndex: indexPath) else {
return UITableViewCell()
}
cell.setupCell(withSong: song)
return cell
}
}
<强>演示强>
class SongListModulePresenter {
weak var view: SongListModuleViewProtocol?
var interactor: SongListModuleInteractorInputProtocol?
var wireFrame: SongListModuleWireFrameProtocol?
var songs: [Song] = []
var songsCount: Int {
return songs.count
}
}
extension SongListModulePresenter: SongListModulePresenterProtocol {
func viewWillAppear() {
interactor?.getSongs()
}
func song(atIndex indexPath: IndexPath) -> Song? {
if songs.indices.contains(indexPath.row) {
return songs[indexPath.row]
} else {
return nil
}
}
}
extension SongListModulePresenter: SongListModuleInteractorOutputProtocol {
func reloadSongs(songs: [Song]) {
self.songs = songs
view?.reloadData()
}
}
<强>交互器强>
class SongListModuleInteractor {
weak var presenter: SongListModuleInteractorOutputProtocol?
var localDataManager: SongListModuleLocalDataManagerInputProtocol?
var songs: [Song] {
get {
return localDataManager?.getSongsFromRealm() ?? []
}
}
}
extension SongListModuleInteractor: SongListModuleInteractorInputProtocol {
func getSongs() {
presenter?.reloadSongs(songs: songs)
}
}
<强>线框强>
class SongListModuleWireFrame {}
extension SongListModuleWireFrame: SongListModuleWireFrameProtocol {
class func configure(_ view: SongListModuleViewProtocol) {
let presenter: SongListModulePresenterProtocol & SongListModuleInteractorOutputProtocol = SongListModulePresenter()
let interactor: SongListModuleInteractorInputProtocol = SongListModuleInteractor()
let localDataManager: SongListModuleLocalDataManagerInputProtocol = SongListModuleLocalDataManager()
let wireFrame: SongListModuleWireFrameProtocol = SongListModuleWireFrame()
view.presenter = presenter
presenter.view = view
presenter.wireFrame = wireFrame
presenter.interactor = interactor
interactor.presenter = presenter
interactor.localDataManager = localDataManager
}
}
答案 2 :(得分:4)
非常好的问题@Matrosov。 首先,我想告诉大家,这一切都与VIPER组件之间的责任隔离有关,例如View,Controller,Interactor,Presenter,Routing。
更多关于品味在开发过程中随时间变化的品味。有许多架构模式,如MVC,MVVP,MVVM等。随着时间的推移,我们的品味发生变化,我们将从MVC变为VIPER。有人从MVVP变为VIPER。
通过使班级人数保持较小的行数来使用您的声音视觉。您可以在ViewController本身中保留数据源方法或创建符合UITableViewDatasoruce协议的自定义对象。
我的目标是保持视图控制器的纤薄,每个方法和类都遵循单一责任原则。
Viper有助于创建高度内聚和低耦合的软件。在使用这种开发模式之前,应该对类之间的责任分配有充分的理解。
一旦您对iOS中的糟糕和协议有基本的了解。你会发现这个模型和MVC一样简单。
答案 3 :(得分:4)
1)首先,View passive
不应该询问Presenter的数据。因此,请将self.presenter.showSongs()
替换为self.presenter.onViewDidLoad()
。
2)在Presenter上,关于onViewDidLoad()
的实现,通常应该调用交互器来获取一些数据。然后,交互者将调用self.presenter.onSongsDataFetched()
3)在Presenter上,关于onSongsDataFetched()
的实现,您应该按照View所需的格式准备数据,然后调用self.view.showSongs(listOfSongs)
4)在您的视图上,关于showSongs(listOfSongs)
的实施,您应该设置self.mySongs = listOfSongs
,然后致电tableView.reloadData()
5)您的TableViewDataSource将在您的数组mySongs
上运行并填充TableView。
有关VIPER架构的更多高级技巧和有用的良好做法,我推荐这篇文章:https://www.ckl.io/blog/best-practices-viper-architecture(包括示例项目)
答案 4 :(得分:0)
创建一个NSObject类并将其用作自定义数据源。在此类中定义委托和数据源。
typealias ListCellConfigureBlock = (cell : AnyObject , item : AnyObject? , indexPath : NSIndexPath?) -> ()
typealias DidSelectedRow = (indexPath : NSIndexPath) -> ()
init (items : Array<AnyObject>? , height : CGFloat , tableView : UITableView? , cellIdentifier : String? , configureCellBlock : ListCellConfigureBlock? , aRowSelectedListener : DidSelectedRow) {
self.tableView = tableView
self.items = items
self.cellIdentifier = cellIdentifier
self.tableViewRowHeight = height
self.configureCellBlock = configureCellBlock
self.aRowSelectedListener = aRowSelectedListener
}
为UITableViewCell中的填充数据声明一个回调类型,另一个回复用于当用户点击一行时。
答案 5 :(得分:0)
以下是我对答案的不同观点:
1,View永远不会向Presenter询问某些内容,View只需要将事件(viewDidLoad()/refresh()/loadMore()/generateCell()
)传递给Presenter,而Presenter会响应View传递给哪些事件。
2,我不认为交互者应该引用Presenter,Presenter通过回调(阻止或关闭)与Interactor通信。