MVVM Design Pattern in iOS
Tags: swift, mvvm, pattern, ios
Published: 2021-11-12 17:10:42

---
title: MVVM Design Pattern in iOS
published: true 
description: MVVM 
tags: #swift #mvvm #pattern #iOS
//cover_image: https://direct_url_to_image.jpg
---

# MVVM ( Model-View-ViewModel )

## You can ask me : "Marcos, what's MVVM? Why use it there? Why not use another?"

  ## Well, adopting a design pattern will depend on many things. Whether it's a business strategy, be size of what will be developed, testing requirements and so on. In this article, i will show some ***concepts*** about MVVM and a ***code*** demonstration.


## Let's go!!!

![Alt Text](https://media.giphy.com/media/TLqkzhMIZxAQg/giphy.gif)


****

## MVVM Flow

1. ViewController / View will have a reference to the ViewModel
2. ViewController / View get some ***user action*** and will call ***ViewModel***
3. ViewModel request some API Service and API Service will sends a response to ViewModel
4. ViewModel will notifies the ViewController / View with binding
5. The ViewController / View will update the UI with data

![1_8MiNUZRqM1XDtjtifxTSqA](https://user-images.githubusercontent.com/34096743/141125060-b219cac2-0295-421f-bcb7-cbea1aff449e.png)

***
## Project Informations

1. Use ***jsonplaceholder*** REST API
2. URLSession to fetch data
3. The table view will show data from service 

****

# MVVM ( Model-ViewModel- Model )


## ***M*** ( Model ) : Represents data. ***JUST*** it. Holds the data and has ***nothing*** and don't have business logic.

```swift
struct Post: Decodable {
  let userId:Int
  let id: Int
  let title: String
  let body: String
}
```
***

## ***V*** ( View ) : Represents the ***UI***. In the view, we have ***User Actions*** that will call view model to call api service. After it, the data will through to view ( view model is responsible to do it ) and shows informations in the screen.


```swift
class PostViewController: UIViewController {

    private var postViewModel: PostViewModel?
    private var postDataSource: PostTableViewDataSource<PostTableViewCell, Post>?
    private var postDelegate: PostTableViewDelegate?
    
    private lazy var postTableView: UITableView = {
       let tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        return tableView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
        setupConstraints()
        updateUI()
    }
    
    private func setupViews() {
        postTableView.register(PostTableViewCell.self, forCellReuseIdentifier: PostTableViewCell.cellIdentifier)
        self.view.addSubview(postTableView)
    }
    
    private func setupConstraints() {
        NSLayoutConstraint.activate([
            postTableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            postTableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
            postTableView.widthAnchor.constraint(equalTo: self.view.widthAnchor),
            postTableView.heightAnchor.constraint(equalTo: self.view.heightAnchor),
            postTableView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
            postTableView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
        ])
    }
    private func updateUI() {
        postViewModel = PostViewModel()
        postDelegate = PostTableViewDelegate()
        postViewModel?.bindPostViewModelToController = { [weak self] in
            self?.updateDataSource()
        }
    }
    
    private func updateDataSource() {
        guard let posts = postViewModel?.getPosts() else { return }
        postDataSource = PostTableViewDataSource(cellIdentifier: PostTableViewCell.cellIdentifier, items: posts, configureCell: { (cell, post) in
            cell.configureCell(post: post)
        })
        
        DispatchQueue.main.async {
            self.postTableView.delegate = self.postDelegate
            self.postTableView.dataSource = self.postDataSource
            self.postTableView.reloadData()
        }
    }
}
```

***PostTableViewCell***

```swift
class PostTableViewCell: UITableViewCell {
    static public var cellIdentifier: String = "PostTableViewCellIdentifier"
    
    private lazy var titleLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 0
        label.lineBreakMode = .byWordWrapping
        label.textColor = .black
        label.font = UIFont.boldSystemFont(ofSize: 13)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private lazy var bodyLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 0
        label.lineBreakMode = .byWordWrapping
        label.textColor = .blue
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private var postStackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.spacing = 3
        stackView.distribution = .fill
        stackView.translatesAutoresizingMaskIntoConstraints = false
        return stackView
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupViews()
        setupConstraints()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        titleLabel.text = ""
        bodyLabel.text = ""
    }
    
    public func configureCell(post: Post) {
        titleLabel.text = post.title
        bodyLabel.text = post.body
    }
    
    private func setupViews() {
        contentView.addSubview(postStackView)
        postStackView.addArrangedSubview(titleLabel)
        postStackView.addArrangedSubview(bodyLabel)
    }
    private func setupConstraints() {
        NSLayoutConstraint.activate([
            postStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            postStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            postStackView.topAnchor.constraint(equalTo: contentView.topAnchor),
            postStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])
    }
}
```

***PostTableViewDataSource***

```swift

class PostTableViewDataSource<CELL: UITableViewCell, T>: NSObject, UITableViewDataSource {
    
    public var cellIdentifier: String
    public var items: Array<T>
    public var configureCell: (CELL, T) -> () = {_,_ in }
    
    init(cellIdentifier: String, items: Array<T>, configureCell: @escaping  (CELL, T) -> () ) {
        self.cellIdentifier = cellIdentifier
        self.configureCell = configureCell
        self.items = items
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? CELL {
            let item = items[indexPath.row]
            self.configureCell(cell, item)
            return cell
        }
        
        return UITableViewCell()
    }
}
```

***PostTableViewDelegate***

```swift
class PostTableViewDelegate: NSObject, UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 300.0
    }
}
```

*** 

## ***VM*** ( ViewModel ) : Responsible to call service class to fetch data from the server.The ViewModel don't know what the views and what thew view does.

```swift
class PostViewModel: NSObject {
    private var postService: PostService?
    private var posts: Array<Post>? {
      didSet {
        self.bindPostToViewController()
      }
    }
    
    override init() {
        super.init()
        self.postService = PostService()
        self.callGetPosts()
        
    }
  
    public var bindPostToViewController: (() -> ()) = {}
    
    private func callGetPosts() {
        postService?.apiToGetPosts { (posts, error) in
            if error != nil {
                self.posts = posts
            }
        }
    }
}
```

***posts is a property observer, so when we have API response, we will populate posts variable and call bindPostToViewController. Once we have data from view model, we can update the UI. bindPostToViewController will tell us when the response is already.***

***PostService***

```swift
class PostService {
    private let postsPath = "https://jsonplaceholder.typicode.com/posts"
    
    public func apiToGetPosts(completion: @escaping([Post]?, Error?) -> ()) {
        guard let url = URL(string: postsPath) else { return }
        
        URLSession.shared.dataTask(with: url) { (data, response ,error) in
            if error != nil { completion(nil, error); return }
            
            guard let dataReceive = data else { return }
            
            do {
                let posts = try JSONDecoder().decode([Post].self, from: dataReceive)
                completion(posts, nil)
            } catch {
                completion(nil, error)
            }
        }.resume()
    }
}
```
***

## Thanks for all! :metal:

***I hope that i help you. Any question, please, tell me in comments***.

[Github Project](https://github.com/mkilmerr/MVVM)