Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS using VIPER with UITableView

Tags:

I have a view controller which contains a table view, so I want to ask where should I put table view data source and delegate, should it be an external object or I can write it in my view controller if we say about VIPER pattern.

Normally using pattern I do this:

In viewDidLoad I request some flow from presenter like self.presenter.showSongs()

Presenter contains interactor and in showSongs method I request some data from interactor like: self.interactor.loadSongs()

When songs are ready to passing back to view controller I use presenter one more time to determine how this data should be display in view controller. But my question what should I do with datasource of table view?

like image 847
Matrosov Oleksandr Avatar asked Jul 21 '16 17:07

Matrosov Oleksandr


2 Answers

First of all your View shouldn't ask data from Presenter - it's violation of VIPER architecture.

The View is passive. It waits for the Presenter to give it content to display; it never asks the Presenter for data.

As for you question: It's better to keep current view state in Presenter, including all data. Because it's providing communications between VIPER parts based on state.

But in other way Presenter shouldn't know anything about UIKit, so UITableViewDataSource and UITableViewDelegate should be part of View layer.

To keep you ViewController in good shape and do it in 'SOLID' way, it's better to keep DataSource and Delegate in separate files. But these parts still should know about presenter to ask data. So I prefer to do it in Extension of ViewController

All module should look something like that:

View

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

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 Interactor - (void)getSongs {    [self.service getSongsWithCompletionHandler:^(NSArray *songs) {       [self.presenter didLoadSongs:songs];     }]; } @end 
like image 156
Konstantin Avatar answered Sep 30 '22 19:09

Konstantin


Example in Swift 3.1, maybe will be useful for someone:

View

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

Wireframe

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     } } 
like image 44
jonaszmclaren Avatar answered Sep 30 '22 19:09

jonaszmclaren