2016-07-21 19 views
14

У меня есть контроллер представления, который содержит табличное представление, поэтому я хочу спросить, где я должен поставить источник данных таблицы и делегировать его, должен ли он быть внешним объектом, или я могу записать его в свой контроллер представления если говорить о шаблоне VIPER.iOS, использующий VIPER с UITableView

Обычно, используя шаблон я это сделать:

В viewDidLoad я запросить некоторый поток от ведущего как self.presenter.showSongs()

Presenter содержит Interactor и в методе showSongs я запросить некоторые данные из интерактора как: self.interactor.loadSongs ()

Когда песни готовы перейти к просмотру контроллера, я использую презентатор еще раз, чтобы определить, как эти данные должны отображаться в контроллере вида. Но мой вопрос, что мне делать с datasource таблицы просмотра?

+0

Какой подход вы в конечном итоге взяли? –

+0

@ Ríomhaire, на данный момент у меня есть экземпляры в моем презентаторе, которые вызвали tableViewDataSource и tableViewDelegate. –

ответ

14

Прежде всего, ваш взгляд не должен запрашивать данные у Ведущего - это нарушение архитектуры VIPER.

Вид является пассивным. Он ждет, пока Презентатор предоставит ему контент для отображения; он никогда не запрашивает у Ведущего данные.

Что касается вопросов: Лучше сохранить текущее состояние просмотра в Presenter, включая все данные. Потому что он обеспечивает связь между частями VIPER на основе состояния.

Но в другом случае докладчик ничего не должен знать о UIKit, поэтому UITableViewDataSource и UITableViewDelegate должны быть частью слоя «Вид».

Чтобы сохранить ViewController в хорошей форме и сделать это с помощью «ТВЕРДОГО» способа, лучше хранить DataSource и делегировать в отдельных файлах. Но эти детали по-прежнему должны знать о презентации, чтобы спросить данные. Так что я предпочитаю делать это в расширении 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

Interactor.m

@implementation Presenter 
- (void)getSongs { 
    [self.service getSongsWithCompletionHandler:^(NSArray *songs) { 
     [self.presenter didLoadSongs:songs]; 
    }]; 
} 
@end 
+0

спасибо за ответ. Я также согласен с вами в том, что datasource/delegate должен быть отдельным файлом. Отличное решение с расширением! –

+0

@MatrosovAlexander это мое решение подходит вам, пожалуйста, отметьте его как принятый ответ – Konstantin

+0

Как указано здесь http://mutualmobile.github.io/blog/2013/12/04/viper-introduction/ Interactor никогда не передает объекты на уровень представления (т.е. Presenter). В вашем случае это так. Как решить эту проблему? –

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 


} 

Объявите два typealias для спины вызова относительно одного для заполнения данных в UITableViewCell и другой для того, когда пользователь вводит строку.

4

Очень хороший вопрос @ Матросов. Прежде всего, я хочу вам сказать, что речь идет о разделении ответственности между компонентами VIPER, такими как View, Controller, Interactor, Presenter, Routing.

Это больше о вкусах, которые меняются за время разработки. Существует много архитектурных шаблонов, таких как MVC, MVVP, MVVM и т. Д. Со временем, когда меняется наш вкус, мы меняем с MVC на VIPER. Кто-то меняется с MVVP на VIPER.

Используйте свое звуковое зрение, сохранив класс в небольшом количестве строк. Вы можете хранить методы источника данных в самом представлении ViewController или создать настраиваемый объект, соответствующий протоколу UITableViewDatasoruce.

Моя цель - держать контроллеры представлений slim и каждый метод и класс следовать принципу единой ответственности.

Viper помогает создавать высокосвязное и малосвязное программное обеспечение.

Прежде чем использовать эту модель развития, необходимо иметь четкое представление о распределении ответственности между классами.

Как только у вас есть базовое понимание Oops and Protocols в iOS. Вы найдете эту модель такой же простой, как MVC.

+0

благодарю вас за ответ! да, я понял, как работает VIPER, но обычно мы получаем данные в сервисах формы взаимодействия, а затем нам нужно обновить модель данных, поэтому я предполагаю, что у моего источника данных должен быть экземпляр взаимодействия, а затем, когда взаимодействующий говорит, что он содержит данные, нам нужно перезагрузить источник данных через контроллер просмотра. –

+0

@MatrosovAlexander Используется для взаимодействия с постоянным и сетевым уровнем. Как только данные будут доступны, сообщите ведущему. Презентатор может сообщить объекту, который соответствует протоколу данных и делегировать протокол. –

3

1) Прежде всего, это View passive не должен спрашивать данные для Presenter. Итак, замените self.presenter.showSongs() на self.presenter.onViewDidLoad().

2) На вашем презентаторе, при реализации onViewDidLoad(), вы обычно должны звонить интерактору для получения некоторых данных. И Interactor будет звонить, например, 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 (пример проекта включен)

5

Пример в 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 
    } 
} 

Presenter

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() 
    } 
} 

Interactor

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 
    } 
} 
0

Вот мои различные точки из ответов:

1, Вид никогда не должен просить Presenter для чего-то, Просто нужно для передачи событий (viewDidLoad()/refresh()/loadMore()/generateCell()) докладчику, а докладчик отвечает, какие события просматривает представление.

2, я не думаю, что Interactor должен иметь ссылку на Presenter, Presenter связывается с Interactor посредством обратных вызовов (блокировки или закрытия).