Last active
November 26, 2018 15:23
-
-
Save Sajjon/dd7da7380702f85af8ba4fb09bf8c2db to your computer and use it in GitHub Desktop.
Medium article: SLC part 1 - ViewController base
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import RxSwift | |
| class SceneController<View>: UIViewController where View: UIView & ViewModelled { | |
| typealias ViewModel = View.ViewModel | |
| private let bag = DisposeBag() | |
| private let viewModel: ViewModel | |
| // We omit some additional "AbstractTarget" wrappers taking these `PublishSubjects` as arguments | |
| // used to create `Selector` for `UIBarButtonItem` creation. | |
| private let rightBarButtonSubject = PublishSubject<Void>() | |
| private let leftBarButtonSubject = PublishSubject<Void>() | |
| // MARK: - Initialization | |
| required init(viewModel: ViewModel) { | |
| self.viewModel = viewModel | |
| super.init(nibName: nil, bundle: nil) | |
| setup() | |
| } | |
| required init?(coder: NSCoder) { fatalError("InterfaceBuilder support omitted.") } | |
| // MARK: View Lifecycle | |
| override func loadView() { | |
| view = View() | |
| view.backgroundColor = .white | |
| // We should not use autolayout here, but this works. | |
| view.bounds = UIScreen.main.bounds | |
| } | |
| override func viewDidLoad() { | |
| super.viewDidLoad() | |
| // Here we can actually also setup titles and navigation bar using some protocol magic. | |
| } | |
| } | |
| // MARK: Private | |
| private extension SceneController { | |
| func setup() { | |
| bindViewToViewModel() | |
| edgesForExtendedLayout = .bottom | |
| } | |
| func bindViewToViewModel() { | |
| guard let contentView = view as? View else { fatalError("Incorrect implementation, set `view` to an instance of `View` inside `loadView()`.") } | |
| // We omit some PublishSubjects for `title` and `navigationBar` items content. | |
| let controllerInput = ControllerInput( | |
| viewDidLoad: rx.viewDidLoad, | |
| viewWillAppear: rx.viewWillAppear, | |
| viewDidAppear: rx.viewDidAppear, | |
| leftBarButtonTrigger: leftBarButtonSubject.asDriverOnErrorReturnEmpty(), | |
| rightBarButtonTrigger: rightBarButtonSubject.asDriverOnErrorReturnEmpty() | |
| ) | |
| // The `View` passed as a generic conforms to `ViewModelled` | |
| // a protocol declaring the `inputFromView` getter. | |
| let inputFromView = contentView.inputFromView | |
| let input = ViewModel.Input(fromView: inputFromView, fromController: controllerInput) | |
| // Transform input from view and controller into output used to update UI | |
| // Navigatoin logic is handled by the Coordinator listening to navigation | |
| // steps in passed to the ViewModels `navigator` (`Stepper`). | |
| let output = viewModel.transform(input: input) | |
| // Update UI, dispose the array of `Disposable`s | |
| contentView.populate(with: output).forEach { $0.disposed(by: bag) } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment