MVVM Guidelines (UIKit)

Cover Image for MVVM Guidelines (UIKit)

Model-View-ViewModel (MVVM) is a software design pattern structured to separate program logic and user interface controls. MVVM, also known as model-view-binder, was created by Microsoft architects to simplify event-driven programming of user interfaces. MVVM helps organize code and break programs into modules to make developing, updating and reusing code more straightforward and faster.

  • View is the collection of visible elements (including animations) that receive user input. (UIView, UIWindow, UIViewController, UIGesture)

  • ViewModel is located between the View and Model layers. This is where the controls for interacting with View are housed, while the binding is used to connect the UI elements in View to the controls in ViewModel.

  • Model houses the logic for the program, which is retrieved by the ViewModel upon its own receipt of input from the user through View. (Repository, APIs, UseCase (Clean architecture))

MVVM

View

  • UI
  • Subscribe using the observable pattern to data changed in the viewModel(which means it owns the DisposeBag (Rx) or Set (Combine), which also means it owns the data for the particular view, and those data will get disposed of once not needed along with the view).
  • It will perform any UI changes (layout change, animation, navigation, etc) instructed by the ViewModel.
  • Should be logicless (as much as possible).
  • Should have UI/Snapshot test (as much as possible).

ViewModel

  • Presentation logic.

  • Transform Input to Output.

    • Input

      • can be any user input through View (keypress/textChanged, tap, trigger, scrolling, etc).
        • Eg:
          • for keypress, it’s a stream of String
          • for tap, it’s a stream of Void
          • for scrolling, it’s a stream of the y position, usually a CGFloat in iOS.
          • can also be binded from the view.
        • Eg.
          • Rx: label.rx.text (Observable<String>), button.rx.tap (Observable<Void>)
          • Combine: label.textPublisher, button.tapPublisher
    • Output can be any information the View (UI State) needs.

      • Eg:
        • for the text needs to be binded to a label, it’s a stream of String (Observable<String> or Driver<String> trait, so it’s shared and performed in the main thread by default, at least for RxCocoa)
        • for the action/instruction needs to be performed by the View, it can be a stream of a certain Enum to represent the Action (Driver<Action)
    • With this Input → Output, we can clearly and immediately see the data flow that are happening.

  • Do the transformation and subscription only when needed. So that a viewModel can generate several sub-view models and can only start processing once the view needs it.

  • It will interact with the use case or other dependencies.

  • It will tell the View of what to do.

  • Should not have any UI inside it.

  • Should have 100% of test coverage.

Model

  • Hold the business logic, use cases and data.

  • A Use case describes how a user wants to "use" a system.

    • Eg.
      • Auth Feature has a use case scenario of:
        • can register via email and password and can return credentials.
        • can register via social media account and can return credentials.
        • can log in via email and password and can return credentials.
        • can log in via social media account and can return credentials.
        • can change the password
        • can delete an account
  • Since Use Case uses one or more dependencies(preferably small services but does specific tasks, microservices), Use Case can and must have 100% test coverage.

Here are some other examples of view models:

Here are test cases:

Other references: