I'm trying to use MVVM with delegate protocols. When something changes in the view model I want to trigger it in the view controller.

When I want to use protocols to handle the view model's event on a view controller, I can not set the protocol to the view controller for my view model class. It gives me the error:

Argument type (SecondViewController) -> () -> SecondViewController does not conform to expected type SecondViewModelEvents

How can I do this the right way?

Here is the code for my view model:

protocol SecondViewModelEvents {
    func changeBackground()
}

class SecondViewModel:NSObject {

    var events:SecondViewModelEvents?

    init(del:SecondViewModelEvents) {
        self.events = del
    }

    func loadDataFromServer() {
        self.events?.changeBackground()
    }

}

And for my view controller class:

class SecondViewController: UIViewController,SecondViewModelEvents {

    let viewModel = SecondViewModel(del: self) //Argument type '(SecondViewController) -> () -> SecondViewController' does not conform to expected type 'SecondViewModelEvents'

    @IBAction func buttonPressed(_ sender: Any) {
        self.viewModel.loadDataFromServer()
    }

    func changeBackground() {
        self.view.backgroundColor = UIColor.red
    }

}

2 Answers

1
Bojan Dimovski On Best Solutions

You're trying to initialize the view model variable and pass the view controller as a delegate which at this point is not fully initialized.

Try checking out the very informative and very detailed Initialization page in the official Swift language guide.

Since this is a protocol used for this specific purpose, we can safely constrain it to classes (notice the : class addition to your code.

protocol SecondViewModelEvents: class {
    func changeBackground()
}

It's good practice to use more descriptive naming, and also using weak references for delegate objects in order to avoid strong reference cycles.

class SecondViewModel {

    weak var delegate: SecondViewModelEvents?

    init(delegate: SecondViewModelEvents) {
        self.delegate = delegate
    }

    func loadDataFromServer() {
        delegate?.changeBackground()
    }

}

You can try to use an optional view model, which will get initialized in an appropriate place, like awakeFromNib():

class SecondViewController: UIViewController, SecondViewModelEvents {

    var viewModel: SecondViewModel?

    override func awakeFromNib() {
        super.awakeFromNib()

        viewModel = SecondViewModel(delegate: self)
    }

    @IBAction func buttonPressed(_ sender: Any) {
        viewModel?.loadDataFromServer()
    }

    func changeBackground() {
        view.backgroundColor = UIColor.red
    }

}

Or an alternative approach would be to initialize a non-optional view model in the UIViewController required initializer:


    // ...

    var viewModel: SecondViewModel

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        self.viewModel = SecondViewModel(delegate: self)
    }

    // ...
1
Kamran On

You need to use lazy initialization as,

lazy var viewModel =  SecondViewModel(del: self)

OR

lazy var viewModel = { [unowned self] in SecondViewModel(del: self) }()