Considerations in implementing MVVM in iOS with Swift

I’ve been researching iOS design patterns searching for new techniques for writing correct, fault-tolerant, and maintainable code as quickly as possible, and I’ve realized a terminological inexactitude has led to some confusion in design discussions I’ve had in the past. I think most people may be talking about implementing a layer of abstraction between the view controller and the model, delegating querying the model for information. This has it’s uses, but what I’ll be covering here is placing a layer of abstraction between the view and controller and calling that a view model.

One popular approach to application architecture in iOS is known as MVVM, or Model-View-ViewModel, developed by Microsoft around 2005. What I thought a view model was about was a “model” of a UIView’s desired input. Roughly, the “view model” of a UILabel would be a String, because you can set and get the string of the label. So imagine you had a custom UIView that looks something like this:

class ProfileView: UIView {
 @IBOutlet weak var userImageView: UIImageView!
 @IBOutlet weak var nameLabel: UILabel!
 @IBOutlet weak var biographyTextView: UITextView!
}

Instead of exposing the image view, label, and text view to the user of this class, which is desirable because the implementation of label or text view or that might very well change in the future, you “model” this view with a lightweight structure that looks something like this:

struct Model {
 let userImage: UIImage?
 let fullName: String?
 let biography: String?
}

Which enables you label the subviews of profile view as private and to write a derived property on your profile view that looks like this:

var model: Model? {
 get {
  return Model(userImage: userImageView.image,
  fullName: nameLabel.text,
  biography: biographyTextView.text)
 }
 set {
  userImageView.image = model?.userImage
  nameLabel.text = model?.fullName
  biographyTextView.text = model?.biography
 }
}

This allows you to hide the implementation of the view to users of it, define how you want the input of a view to be structured (“full name” or “first name” and “last name”, for instance), and you can then write extensions on your actual model layer to get the input you want. Like this, for instance:

class User {
 var image: UIImage?
 var firstName: String?
 var lastName: String?
 var biography: String?
}

extension User {
 var profileViewModel: ProfileView.Model {
   return ProfileView.Model(userImage: image,
                            fullName: "\(firstName) \(lastName)",
                            biography: biography)
 }
}

Another way of implementing this pattern that might have some benefits if you’re interested in testing is with protocols. You won’t be able to add the model as a nested class of your view, which can make organizing code easier. Instead of having the view model generated in an extension, you could make the view model a protocol:

protocol ProfileViewModelProtocol {
 var userImage: UIImage? { get }
 var fullName: String? { get }
 var biography: String? { get }
}

And then instead of making the view model a derived property on the user, you add conformance to this protocol in an extension on your model:

extension User: ProfileViewModelProtocol {
 var userImage: UIImage? {
   return image
 }
 var fullName: String? {
   return "\(firstName ?? "") \(lastName ?? "")"
 }
 var biography: String? {
   return bio
 }
}

And then the property of the profile view becomes something like this:

 var model: ProfileViewModelProtocol? {
  didSet {
    userImageView.image = model?.userImage
    nameLabel.text = model?.fullName
    biographyTextView.text = model?.biography
   }
 }

But this only defines communication one way. Models change. So how does this approach to MVVM tackle that? Let’s say the user can modify the biography in this profile. We’ll need to implement UITextViewDelegate on ProfileView and define a ProfileViewDelegate:

protocol ProfileViewDelegate: NSObjectProtocol {
 func biographyDidChange(_ biography: String)
}

class ProfileView: UIView, UITextViewDelegate {
  /* ... */
 weak var delegate: ProfileViewDelegate?
 
 func textViewDidChange(_ textView: UITextView) {
   delegate?.biographyDidChange(textView.text)
 }
}

And the pattern all comes together in our hypothetical ProfileViewController:

class ProfileViewController: UIViewController, ProfileViewDelegate {
 @IBOutlet fileprivate weak var profileView: ProfileView!
 var user: User?
 override func viewDidLoad() {
   super.viewDidLoad()
   profileView.model = user
 }
 
 func biographyDidChange(_ biography: String) {
   user?.bio = biography
   // inform API
 }
}

To make this version clear, here’s a nice graphic I made for you:

This graphic makes the benefits clear in that there’s a level of abstraction in-between the view and the controller which is responsible for translating between the model layer and the view layer. If I find a good example of the other variety of MVVM, I’ll investigate and write up a post about it. Here’s a playground with all the code from this post.

Leave a comment

Your email address will not be published. Required fields are marked *