Chapter 10: Model-View-ViewModel Pattern
大綱
When should you use it?
Model-View-ViewModel (MVVM) is a structural design pattern that separates objects into three distinct groups:
Models : hold app data. They’re usually structs or simple classes.
Views: display visual elements and controls on the screen. They’re typically subclasses of UIView.
View models: transform model information into values that can be displayed on a view. They’re usually classes, so they can be passed around as references.
view controllers: do exist in MVVM, but their role is minimized.
When should you use it?
Use this pattern when you need to transform models into another representation for a view.
MVVM is a great way to slim down massive view controllers that require several model-to-view transformations
Playground example
Model
// MARK: - Model
public class Pet {
public enum Rarity {
case common
case uncommon
case rare
case veryRare
}
public let name: String
public let birthday: Date
public let rarity: Rarity
public let image: UIImage
public init(name: String,
birthday: Date,
rarity: Rarity,
image: UIImage) {
self.name = name
self.birthday = birthday
self.rarity = rarity
self.image = image
}
}
ViewModel
// MARK: - ViewModel
public class PetViewModel {
private let pet: Pet
private let calendar: Calendar
public init(pet: Pet) {
self.pet = pet
self.calendar = Calendar(identifier: .gregorian)
}
// declared two computed properties for name and image, where you return the pet’s name and image respectively.
public var name: String {
return pet.name
}
public var image: UIImage {
return pet.image
}
// another computed property
public var ageText: String {
let today = calendar.startOfDay(for: Date())
let birthday = calendar.startOfDay(for: pet.birthday)
let components = calendar.dateComponents([.year],
from: birthday,
to: today)
let age = components.year!
return "\(age) years old"
}
// computed property
public var adoptionFeeText: String {
switch pet.rarity {
case .common:
return "$50.00"
case .uncommon:
return "$75.00"
case .rare:
return "$150.00"
case .veryRare:
return "$500.00"
}
}
}
extension PetViewModel {
// configue ViewModel到View中
public func configure(_ view: PetView) {
view.nameLabel.text = name
view.imageView.image = image
view.ageLabel.text = ageText
view.adoptionFeeLabel.text = adoptionFeeText
}
}
View
// MARK: - View
public class PetView: UIView {
public let imageView: UIImageView
public let nameLabel: UILabel
public let ageLabel: UILabel
public let adoptionFeeLabel: UILabel
public override init(frame: CGRect) {
var childFrame = CGRect(x: 0,
y: 16,
width: frame.width,
height: frame.height / 2)
imageView = UIImageView(frame: childFrame)
imageView.contentMode = .scaleAspectFit
childFrame.origin.y += childFrame.height + 16
childFrame.size.height = 30
nameLabel = UILabel(frame: childFrame)
nameLabel.textAlignment = .center
childFrame.origin.y += childFrame.height
ageLabel = UILabel(frame: childFrame)
ageLabel.textAlignment = .center
childFrame.origin.y += childFrame.height
adoptionFeeLabel = UILabel(frame: childFrame)
adoptionFeeLabel.textAlignment = .center
super.init(frame: frame)
backgroundColor = .white
addSubview(imageView)
addSubview(nameLabel)
addSubview(ageLabel)
addSubview(adoptionFeeLabel)
}
@available(*, unavailable)
public required init?(coder: NSCoder) {
fatalError("init?(coder:) is not supported")
}
}
Configure
// MARK: - Example
let birthday = Date(timeIntervalSinceNow: (-2 * 86400 * 366))
let image = UIImage(named: "stuart")!
let stuart = Pet(name: "Stuart",
birthday: birthday,
rarity: .veryRare,
image: image)
let viewModel = PetViewModel(pet: stuart)
let frame = CGRect(x: 0, y: 0, width: 300, height: 420)
let view = PetView(frame: frame)
viewModel.configure(view)
PlaygroundPage.current.liveView = view
What should you be careful about?
MVVM works well if your app requires many model-to-view transformations.
However, not every object will neatly fit into the categories of model, view or view model. Instead, you should use MVVM in combination with other design patterns.
Tutorial project
CoffeeQuest
Key points
MVVM helps slim down view controllers, making them easier to work with. Thus combatting the "Massive View Controller" problem.
View models are classes that take objects and transform them into different objects, which can be passed into the view controller and displayed on the view. They’re especially useful for converting computed properties such as Date or Decimal into a String or something else that actually can be shown in a UILabel or UIView.
If you’re only using the view model with one view, it can be good to put all the configurations into the view model. However, if you’re using more than one view, you might find that putting all the logic in the view model clutters it. Having the configure code separated into each view may be simpler.
MVC may be a better starting point if your app is small. As your app’s requirements change, you’ll likely need to choose different design patterns based on your changing requirements.
Last updated
Was this helpful?