Chapter 3. Binder. - dmsl1805/Cookbook GitHub Wiki

Here is a definition of ViewControllerBinder protocol. It is so common to touch the view of the view controller inside of the “viewDidLoad”. There are rare cases where we use a different approach, but the current one suits 99% of the time.

import RxSwift

protocol ViewControllerBinder: Disposable {
    associatedtype DisposeViewControllerContainer: UIViewController, DisposeContainer
    
    var viewController: DisposeViewControllerContainer { get }
    
    func bindLoaded()
}
import RxViewController

extension ViewControllerBinder where Self: AnyObject {
    func bind() {
        viewController.rx.viewDidLoad
            .subscribe(onNext: unowned(self, in: Self.bindLoaded))
            .disposed(by: viewController.bag)
    }
    
}

And how actual logic can be implemented.

import Foundation
import Nuke

final class MovieDetailBinder: ViewControllerBinder {
    unowned let viewController: MovieDetailViewController
    private let driver: MovieDetailDriving
    
    init(viewController: MovieDetailViewController,
         driver: MovieDetailDriving) {
        self.viewController = viewController
        self.driver = driver
        bind()
    }
    
    func dispose() { }
    
    func bindLoaded() {
        // 1. Style
        viewController.statusBarStyle = .lightContent
        
        // 2. State
        viewController.bag.insert(
            viewController.rx.viewWillAppear
                .bind(onNext: unowned(self, in: MovieDetailBinder.viewWillAppear)),
            driver.data
                .drive(onNext: unowned(self, in: MovieDetailBinder.configure))
        )
        
        // 3. Action
        viewController.bag.insert(
            viewController.backButton.rx.tap
                .bind(onNext: driver.close)
        )
    }
    
    private func viewWillAppear(_ animated: Bool) {
        viewController.navigationController?.setNavigationBarHidden(true, animated: animated)
    }
    
    private func configure(_ data: MovieDetailData) {
        viewController.headerView.configure(with: data)
        viewController.tipsView.configure(with: data)
        if let url = data.posterUrl {
            Nuke.loadImage(with: URL(string: url)!, into: viewController.posterImageView)
        }
    }
}

Here is what this binder does:

  1. Apply some static style properties
  2. Bind state changes from Driver to View
  3. Bind actions from View to Driver

Want even more separation of UI logic? Look further.