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/