Skip to content

Chapter 3. Binder.

Dmitriy Shulzhenko edited this page Sep 27, 2020 · 4 revisions

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.

Clone this wiki locally