iOS使用VIPER和UITableView

Mat*_*der 19 viper ios viper-architecture

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

通常使用模式我这样做:

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

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

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

Kon*_*tin 19

首先,您的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
Run Code Online (Sandbox Code Playgroud)

ViewController.m

NSString * const TableViewCellIdentifier = @"CellIdentifier";

@implemntation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   [self.presenter setupView];
}

- (void)refreshSongs {
   [self.tableView reloadData];
}

@end
Run Code Online (Sandbox Code Playgroud)

视图控制器+ TableViewDataSource.h

@interface ViewController (TableViewDataSource) <UITableViewDataSource>
@end
Run Code Online (Sandbox Code Playgroud)

视图控制器+ 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
Run Code Online (Sandbox Code Playgroud)

视图控制器+ TableViewDelegate.h

@interface ViewController (TableViewDelegate) <UITableViewDelegate>
@end
Run Code Online (Sandbox Code Playgroud)

视图控制器+ 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
Run Code Online (Sandbox Code Playgroud)

主持人

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
Run Code Online (Sandbox Code Playgroud)

交互器

Interactor.m

@implementation Interactor
- (void)getSongs {
   [self.service getSongsWithCompletionHandler:^(NSArray *songs) {
      [self.presenter didLoadSongs:songs];
    }];
}
@end
Run Code Online (Sandbox Code Playgroud)

  • 在这个调用`Song*song = [self.presenter songAtIndex:[indexPath.row]];`中,实际上是要求演示者提供数据的视图.与你的第一句话相反. (8认同)

jon*_*ren 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
    }
}
Run Code Online (Sandbox Code Playgroud)

主持人

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()
    }
}
Run Code Online (Sandbox Code Playgroud)

交互器

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

线框

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
    }
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*tti 5

1)首先,View是passive不应该为Presenter询​​问数据的。因此,替换self.presenter.showSongs()self.presenter.onViewDidLoad()

2)在您的Presenter上,在实现时,onViewDidLoad()通常应调用交互程序以获取一些数据。然后,交互者将呼叫例如self.presenter.onSongsDataFetched()

3)在Presenter上,在实现onSongsDataFetched()时应按照视图所需的格式准备数据,然后调用self.view.showSongs(listOfSongs)

4)在您的View上,在的实现上showSongs(listOfSongs),应先设置self.mySongs = listOfSongs然后调用tableView.reloadData()

5)您的TableViewDataSource将在您的数组上运行mySongs并填充TableView。

有关VIPER架构的更多高级技巧和有用的良好实践,我建议发布此帖子:https : //www.ckl.io/blog/best-practices-viper-architecture(包括示例项目)