ViewModel

ViewModel, as mentioned previously, is meant to provide a usable representation of Model to the View layer. It will also provide a way to bind itself to events. In Swift, we can use closures, in order to attach the callbacks to ViewModel:

class ViewModel {
Other solutions, including two-way data binding, Observables, and Functional Reactive Programming, provide abstractions over the manual binding example that we'll be performing here. If you were to use MVVM extensively, I'd recommend that you properly leveraged two-way data binding.

Similar to ApplicationController in the MVC pattern, we'll keep track of the main Model manager, which is the QuestionController in ViewModel:

    private let questions = QuestionController()

Keeping track of currentQuestion is crucial in ViewModel, as it should represent the state of View at anytime. We'll use the didSet feature of Swift members, in order to fire the onQuestionChanged callback:

    private var currentQuestion: Question? = nil {
didSet { onQuestionChanged?() }
}

The next two declarations will make the most of the public API. The viewModel exposes two callbacks, to indicate internal changes in viewModel. These callbacks will be triggered whenever the program advances to the next question, and when an answer has been given by the user:

    var onQuestionChanged: (() -> Void)?
var onAnswer: ((Bool) -> Void)?

Unlike the MVC example, the Question model is not exposed to View, as a single parameter is needed to display the question to the user. This is a deliberate design choice; depending on your case, you'll probably resort to an intermediate representation of your model. Using intermediate representations makes your views more flexible to work with:

    func getQuestionText() -> String? {
return currentQuestion?.question
}

The rest of the logic of ViewModel exposes the start() method, which starts the game and provides a way to read the answer from the command line:

    func start() {
while let question = nextQuestion() {
waitForAnswer(question: question)
}
}

private func waitForAnswer(question: Question) {
let result = readLine()
onAnswer?(question.isGoodAnswer(result: result))
}

private func nextQuestion() -> Question? {
currentQuestion = questions.next()
return currentQuestion
}
}

The viewModel is now feature-complete. It abstracts all of the undesired parts of the model away from the View layer, and exposes simple callbacks and objects for the view to work with.

In the ViewModel, the readline() call is hardcoded, making this viewModel unsuitable for use in a context other than the command line. There are multiple ways to refactor it, by subclassing or injection.

As you may have guessed, some refactoring will be needed in the View layer, in order to integrate the implementation of ViewModel.