【Xcode/Swift】Useful ViewModel Structure in MVVM Pattern

As mobile developers, we are always searching for the best architectural pattern to organize our code and make it more maintainable and scalable. One of the most popular architectures used in the iOS community is MVVM (Model-View-ViewModel). In this blog post, we will explore a useful ViewModel structure that can make our MVVM code even cleaner and more understandable. To better understand this ViewModel structure, we will also build a simple app using RxSwift and RxCocoa.

ViewModel Structure

The ViewModel is the heart of the MVVM architecture, responsible for presenting the data and logic to the View. To make our ViewModel code more organized and modular, we will split it into two protocols: TestViewModelInputs and TestViewModelOutputs. The TestViewModelInputs protocol will define the inputs that our ViewModel needs to perform its logic, while the TestViewModelOutputs protocol will define the outputs that our ViewModel will provide to the View.

Next, we will create a TestViewModelType protocol that combines the TestViewModelInputs and TestViewModelOutputs protocols. This protocol will be implemented by our ViewModel class, and it will expose its inputs and outputs through computed properties.

Finally, we will create the TestViewModel class that implements the TestViewModelType protocol. This class will contain our business logic, and it will be responsible for transforming the inputs into the outputs.

Let’s take a look at the code:

import RxSwift
import RxCocoa

protocol TestViewModelInputs: AnyObject {
    // Define the inputs here
}

protocol TestViewModelOutputs: AnyObject {
    // Define the outputs here
}

protocol TestViewModelType: AnyObject {
    var inputs: TestViewModelInputs { get }
    var outputs: TestViewModelOutputs { get }
}

class TestViewModel: TestViewModelType, TestViewModelInputs, TestViewModelOutputs {

    var inputs: TestViewModelInputs { return self }
    var outputs: TestViewModelOutputs { return self }

    // Implement the inputs and outputs here
    // Use RxSwift to transform the inputs into the outputs
}

As you can see, we created the TestViewModelInputs and TestViewModelOutputs protocols, the TestViewModelType protocol, and the TestViewModel class that implements them.

To make our ViewModel useful, we need to define its inputs and outputs. Let’s create a simple app to understand this better.

Our app will have a UITextField and a UILabel. Whenever the user types something in the UITextField, the UILabel will display the length of the input string.

Let’s start by creating a new project in Xcode and adding a UIViewController to the storyboard. Then, add a UITextField and a UILabel to the view controller and create outlets for them.

Next, let’s create the TestViewModel class and implement its inputs and outputs:

class TestViewModel: TestViewModelType, TestViewModelInputs, TestViewModelOutputs {

    var inputs: TestViewModelInputs { return self }
    var outputs: TestViewModelOutputs { return self }

    // Inputs
    let textChanged = PublishRelay<String>()

    // Outputs
    let textCount: Driver<String>

    init() {
        textCount = textChanged
            .map { $0.count }
            .map { "Text count: \($0)" }
            .asDriver(onErrorJustReturn: "Error")
    }
}

In this example, we defined the textChanged input as a PublishRelay that emits a String every time the text in the UITextField changes.

For the output, we defined the textCount property as a Driver of String. We used RxSwift operators to transform the `text

countinput into aStringwith the text count, and then we used theasDriveroperator to make it easier to bind to theUILabel` in our View. We also provided a default value in case of errors.

Finally, we need to bind our ViewModel to the View. In the viewDidLoad method of our UIViewController, we can create an instance of the TestViewModel, and then bind its inputs and outputs to the appropriate UI elements:

import UIKit
import RxSwift
import RxCocoa

class HomeViewController: UIViewController {

    @IBOutlet private weak var textField: UITextField!
    @IBOutlet private weak var textCountLabel: UILabel!

    private let viewModel = TestViewModel()
    private let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Bind inputs
        textField.rx.text
            .orEmpty
            .bind(to: viewModel.textChanged)
            .disposed(by: disposeBag)

        // Bind outputs
        viewModel.textCount
            .drive(textCountLabel.rx.text)
            .disposed(by: disposeBag)
    }

}

In this code, we used the RxSwift and RxCocoa libraries to bind the UITextField and the UILabel to the textChanged input and the textCount output of our ViewModel, respectively.

Conclusion

In this blog post, we explored a useful ViewModel structure that can make our MVVM code even cleaner and more understandable. By splitting our ViewModel into two protocols – TestViewModelInputs and TestViewModelOutputs – and implementing the TestViewModelType protocol to combine them, we can create modular and organized ViewModel code that is easier to maintain and understand.

We also built a simple app to demonstrate how to use this ViewModel structure with RxSwift and RxCocoa. By binding the inputs and outputs of our ViewModel to the appropriate UI elements in our View, we can create a reactive and responsive user interface that updates in real-time as the user interacts with it.

I hope this blog post was helpful in understanding how to create a useful ViewModel structure in MVVM. With this approach, you can create well-organized and modular code that is easier to maintain and scale as your app grows in complexity.

この記事は役に立ちましたか?

はい
いいえ
貴重なフィードバックありがとうございます!