web-dev-qa-db-ja.com

UITableViewでVIPERを使用するiOS

テーブルビューを含むビューコントローラーがあるので、テーブルビューのデータソースとデリゲートをどこに置くべきか、それが外部オブジェクトなのか、それともVIPERパターンと言えばビューコントローラーに書き込むことができるのかを確認したいと思います。

通常、パターンを使用してこれを行います:

ViewDidLoadで、self.presenter.showSongs()のようなプレゼンターにいくつかのフローを要求します

Presenterにはインタラクターが含まれており、showSongsメソッドでは次のようなインタラクターにデータを要求します:self.interactor.loadSongs()

曲がView Controllerに戻る準備ができたら、Presenterをもう一度使用して、このデータをView Controllerに表示する方法を決定します。しかし、私の質問はテーブルビューのデータソースで何をすべきですか?

20

まず、ビューはPresenterからのデータを要求するべきではありません-これはVIPERアーキテクチャの違反です。

ビューはパッシブです。プレゼンターがコンテンツを表示するのを待つ。プレゼンターにデータを要求することはありません。

あなたの質問については:すべてのデータを含め、現在のビューステートをPresenterに保持することをお勧めします。状態に基づいてVIPERパーツ間の通信を提供しているためです。

ただし、他の方法では、PresenterはUIKitについて何も認識していないため、UITableViewDataSourceおよびUITableViewDelegateはビューレイヤーの一部である必要があります。

ViewControllerを適切な形に保ち、それを「SOLID」の方法で実行するには、DataSourceとDelegateを別々のファイルに保持することをお勧めします。しかし、これらの部分は、データを要求するプレゼンターについてまだ知っている必要があります。だから私は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
19
Konstantin

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
    }
}
8
jonaszmclaren

1)まず、Viewはpassiveであり、プレゼンターにデータを要求するべきではありません。したがって、self.presenter.showSongs()self.presenter.onViewDidLoad()に置き換えます。

2)プレゼンターのonViewDidLoad()の実装では、通常、インタラクターを呼び出してデータをフェッチする必要があります。そしてインタラクターは、例えば、self.presenter.onSongsDataFetched()を呼び出します

3)プレゼンターのonSongsDataFetched()の実装では、ビューで必要な形式に従ってデータを準備し、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

非常に良い質問@Matrosov。まず最初にお伝えしたいのは、View、Controller、Interactor、Presenter、RoutingなどのVIPERコンポーネント間の責任の分離についてです。

それは、開発中の時間とともに変化する味に関するものです。 MVC、MVVP、MVVMなど、多くのアーキテクチャパターンが世の中に存在します。味が変わると、MVCからVIPERに変わります。誰かがMVVPからVIPERに変わります。

クラスのサイズを行数を少なくして、サウンドビジョンを使用します。データソースメソッドをViewController自体に保持するか、UITableViewDatasoruceプロトコルに準拠するカスタムオブジェクトを作成できます。

ビューコントローラーをスリムに保ち、すべてのメソッドとクラスを単一責任の原則に従うという私の目標。

Viperは、凝集性が高く、結合度の低いソフトウェアの作成に役立ちます。

この開発モデルを使用する前に、クラス間の責任の分散について十分に理解しておく必要があります。

Oopsとプロトコル)の基本を理解したら、このモデルはMVCと同じくらい簡単です。

4
Sandeep Ahuja

答えと私の異なる点は次のとおりです。

1、ViewはPresenterに何かを要求することはありません。Viewは、events(viewDidLoad()/refresh()/loadMore()/generateCell())をPresenterに渡すだけでよく、PresenterはViewが渡したイベントに応答します。

2、私はインタラクターがプレゼンターへの参照を持つべきではないと思います、プレゼンターはコールバック(ブロックまたはクロージャー)を介してインターアクターと通信します。

0
Meonardo

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


}

1つはUITableViewCellのデータの塗りつぶし用、もう1つはユーザーが行をタップしたときのコールバック用の2つのタイプエイリアスを宣言します。

0
Haspinder Singh