Skip to content

Instantly share code, notes, and snippets.

@Sajjon
Last active November 26, 2018 15:23
Show Gist options
  • Save Sajjon/dd7da7380702f85af8ba4fb09bf8c2db to your computer and use it in GitHub Desktop.
Save Sajjon/dd7da7380702f85af8ba4fb09bf8c2db to your computer and use it in GitHub Desktop.
Medium article: SLC part 1 - ViewController base
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