iOS使用VIPER和UITableView

时间:2016-07-21 17:05:58

标签: ios viper viper-architecture

我有一个包含表视图的视图控制器,所以我想问一下我应该把表视图数据源和委托放在哪里,如果它是外部对象,或者如果我们说VIPER,我可以在视图控制器中写它图案。

通常使用模式我这样做:

在viewDidLoad中,我从演示者那里请求一些流程,如self.presenter.showSongs()

Presenter包含交互器,在showSongs方法中,我从交互器请求一些数据,如:self.interactor.loadSongs()

当歌曲准备好传回视图控制器时,我再次使用演示者来确定如何在视图控制器中显示这些数据。但我的问题是如何处理表视图的数据源?

6 个答案:

答案 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通信。