3

I have a dependency problem with my UIViewController and my view model. Basically I want to listen the viewDidLoad event inside my view model. At the moment I have a Class A which instantiates view model and UIViewController with parameter the viewModel, so:

let viewModel = ViewModel()
let viewController = UIViewController(viewModel)

and I've created a RxCocoa extension for the viewDidLoad:

var viewDidLoad: Observable<Void> {
    return self.sentMessage(#selector(Base.viewDidLoad)).map { _ in Void() }
}

now I'm stuck to bind this rx.viewDidLoad to an observable inside my view model. I am able to do it with Subjects but I want a reactive approach using just Observable. I know that I could inject rx.viewDidLoad as constructor parameter of the view model but in this way I'd break my architecture and I don't want to allow the UIViewController to instantiate the view model internally but I want to keep it as a injected dependency.

Any suggestions? Thanks

Solution

Thank to @tomahh I've used this solution:

My view controller:

override func configure(viewModel: ViewModel) {
    viewModel.bindViewDidLoad(rx.viewDidLoad)
}

My view model:

func bindViewDidLoad(_ viewControllerDidLoad: Observable<Void>) {
    //Create observers which depend on viewControllerDidLoad
}
Marco Santarossa
  • 4,058
  • 1
  • 29
  • 49

3 Answers3

4

Because ViewController already knows about view model, it could set a property on ViewModel at initialisation time

class ViewController: UIViewController {
  init(_ viewModel: ViewModel) {
    viewModel.viewDidLoad = self.rx.viewDidLoad
  }
}

And then, observables in ViewModel could be defined as computed property deriving viewDidLoad

struct ViewModel {
  var viewDidLoad: Observable<Void> = .never()

  var something: Observable<String> {
     return viewDidLoad.map { "Huhu, something is guuut" }
  }
}
tomahh
  • 13,441
  • 3
  • 49
  • 70
  • I'm wondering if it's safe to allow the clients of the view model setting the `viewDidLoad` from outside. And if I use `something` before setting `viewDidLoad` then I wouldn't receive the events emitted by the `viewDidLoad`, would I ? Thank you for your answer btw – Marco Santarossa Feb 19 '17 at 14:17
  • Actually I could solve these problems using a view model method like `setViewDidLoad(..)` where I set both `viewDidLoad` and `something` internally – Marco Santarossa Feb 19 '17 at 14:21
  • You're making a valid point about using something before setting `viewDidLoad`. Maybe another way would be to use builder methods for something? Like `func something(using viewDidLoad: Observable) -> Observable`? – tomahh Feb 19 '17 at 14:38
  • I've used a view model method to bind the controller `viewDidLoad` and I could accept this new observable "lifecycle" as a good trade-off, I got rid of the Subject in this way. Thank you – Marco Santarossa Feb 19 '17 at 14:43
  • But what do you think about the function approach? I actually like it better than what is suggested in the answer. I could update it if you agree. – tomahh Feb 19 '17 at 14:49
  • I've edited my question adding my approach but feel free to update your answer. Depends if you prefer yours or "my" approach. Anyway I consider yours as accepted one since you gave me the right input for the solution. Up to you – Marco Santarossa Feb 19 '17 at 14:56
2

If anybody needs that rx properties here is a ready to use solution, inspired by the code of @marco-santarossa

extension Reactive where Base: UIView {
    var willMoveToWindow: Observable<Bool> {
        return self.sentMessage(#selector(Base.willMove(toWindow:)))
            .map({ $0.filter({ !($0 is NSNull) }) })
            .map({ $0.isEmpty == false })
    }
    var viewWillAppear: Observable<Void> {
        return self.willMoveToWindow
            .filter({ $0 })
            .map({ _ in Void() })
    }
    var viewWillDisappear: Observable<Void> {
        return self.willMoveToWindow
            .filter({ !$0 })
            .map({ _ in Void() })
    }
}
Adam Smaka
  • 5,977
  • 3
  • 50
  • 55
1
let viewDidAppear = rx.sentMessage(#selector(UIViewController.viewDidAppear(_:)))
        .mapToVoid()
        .asDriver(onErrorJustReturn: ())
Bassant Ashraf
  • 1,531
  • 2
  • 16
  • 23