5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【アーキテクチャ】MVVM【Swift】

Last updated at Posted at 2021-05-20

MVVMとは?

プログラムを3つの要素、Model、View、ViewModel に分割
各要素は、単方向に依存している View -> ViewModel -> Model
MVVMは、あくまでUI周り構成について触れているだけであって、Modelの中身については、各自で考える必要がある

サンプルコードと簡単な概念

文章だとイメージつかないので実際にソースコード使って書いてみました。

Model

ViewとViewModelがやること以外全て
データ本体、加工、取得、保存などは、全てMdoelに記載

import Foundation

enum Gender {
    case male, female, unspecified
}

struct Person {
    let name: String
    let birthDate: Date?
    let middleName: String?
    let address: String?
    let gender: Gender
    
    var username = "Hachi"
    
    init(name: String,
         birthDate: Date? = nil,
         middleName: String? = nil,
         address: String? = nil,
         gender: Gender = .unspecified) {
        self.name = name
        self.birthDate = birthDate
        self.middleName = middleName
        self.address = address
        self.gender = gender
    }
}

ViewModel

UIに描画するのに必要な情報を準備、保持する

import UIKit

struct PersonFollowingTableViewCellViewModel {
    let name: String
    let username: String
    let currentlyFollowing: Bool
    let image: UIImage?
    
    init(with model: Person) {
        name = model.name
        username = model.username
        currentlyFollowing = false
        image = UIImage(systemName: "person")
    }
}

View

ViewModelの情報を使用してUIを描画

import UIKit

class PersonFollowingTableViewCell: UITableViewCell {
    
    static let identifier = "PersonFollowingTableViewCell"
    
    private let userImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.clipsToBounds = true
        imageView.contentMode = .scaleAspectFit
        return imageView
    }()
    
    private let nameLabel: UILabel = {
        let label = UILabel()
        label.textColor = .label
        return label
    }()
    
    private let usernameLabel: UILabel = {
        let label = UILabel()
        label.textColor = .secondaryLabel
        return label
    }()
    
    private let button: UIButton = {
        let button = UIButton()
        return button
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        contentView.addSubview(userImageView)
        contentView.addSubview(nameLabel)
        contentView.addSubview(usernameLabel)
        contentView.addSubview(button)
        contentView.clipsToBounds = true
    }
    
    required init?(coder: NSCoder) {
        fatalError()
    }
    
    func configure(with viewModel: PersonFollowingTableViewCellViewModel) {
        nameLabel.text = viewModel.name
        usernameLabel.text = viewModel.username
        userImageView.image = viewModel.image
        
        if viewModel.currentlyFollowing {
            button.setTitle("unFollow", for: .normal)
            button.setTitleColor(.black, for: .normal)
            button.layer.borderWidth = 1
            button.layer.borderColor = UIColor.black.cgColor
        } else {
            button.setTitle("Follow", for: .normal)
            button.setTitleColor(.white, for: .normal)
            button.backgroundColor = .link
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        let imageWidth = contentView.frame.size.height - 10
        
        userImageView.frame = CGRect(
            x: 5,
            y: 5,
            width: imageWidth,
            height: imageWidth
        )
        nameLabel.frame = CGRect(
            x: imageWidth + 10,
            y: 0,
            width: contentView.frame.size.width - imageWidth,
            height: contentView.frame.size.height / 2
        )
        usernameLabel.frame = CGRect(
            x: imageWidth + 10,
            y: contentView.frame.size.height / 2,
            width: contentView.frame.size.width - imageWidth,
            height: contentView.frame.size.height / 2
        )
        button.frame = CGRect(
            x: contentView.frame.size.width - 120,
            y: 10,
            width: 110,
            height: contentView.frame.size.height - 20)
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
    }

}
import UIKit

class ViewController: UIViewController, UITableViewDataSource {
    
    private var models = [Person]()
    
    private let tableView: UITableView = {
        let table = UITableView()
        table.register(PersonFollowingTableViewCell.self, forCellReuseIdentifier: PersonFollowingTableViewCell.identifier)
        return table
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        configureModels()
        view.addSubview(tableView)
        tableView.dataSource = self
        tableView.frame = view.bounds
    }
    
    private func configureModels() {
        let names = [
            "Hanako", "Momo", "Leo", "Kotaro", "Malon"
        ]
        
        for name in names {
            models.append(Person(name: name))
        }
        
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return models.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let model = models[indexPath.row]
        guard let cell = tableView.dequeueReusableCell(
            withIdentifier: PersonFollowingTableViewCell.identifier,
            for: indexPath
        ) as? PersonFollowingTableViewCell else {
            return UITableViewCell()
        }
        cell.configure(with: PersonFollowingTableViewCellViewModel(with: model))
        return cell
    }

}

ViewModelとModelはどう違うのか?

ViewModelはViewに表示する情報を保持しますが、ModelはViewには表示しないけどアプリに必要なデータ構造、データ処理、そのほかメソッド・コマンド・プロパティなどを保持します。
ViewModelはViewとModelを繋ぐ役割をしており、ViewModelではViewに表示させるデータを保持します。

MVVMのメリット

  • 可読性の向上
  • テストの容易性
  • Viewへの更新通知、指示の部部分のコードが不要。

MVVMのデメリット

  • 小規模、少人数のプロジェクトではコストに対してリターンが少ない。

参考

https://teratail.com/questions/216820#:~:text=%E3%81%96%E3%81%A3%E3%81%8F%E3%82%8A%E8%AA%AC%E6%98%8E%E3%81%99%E3%82%8B%E3%81%A8Model%E3%82%AF%E3%83%A9%E3%82%B9,%E3%82%92%E3%81%99%E3%82%8B%E3%81%AE%E3%81%BF%E3%81%AB%E7%95%99%E3%82%81%E3%82%8B%E3%80%82
https://qiita.com/ebi-toro/items/046c9a43b15b9e376198
https://www.youtube.com/watch?v=qzXJckVxE4w
https://laptrinhx.com/iosapuriakitekucha-bi-jiao-jian-tao-cocoa-mvc-mvvm-mvp-cleanarchtecture-3832902000/

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?