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.
Does this pattern sound familiar? Yep, it’s very similar to Model-View-Controller (MVC). Note that the class diagram at the top of this page includes a view controller; view controllers do exist in MVVM, but their role is minimized.
In this chapter, you’ll learn how to implement view models and organize your projects to include them. You’ll start with a simple example on what a view model does, then you’ll take a MVC project and refactor it into MVVM.
When should you use it?
Use this pattern when you need to transform models into another representation for a view. For example, you can use a view model to transform a Date into a date-formatted String, a Decimal into a currency-formatted String, or many other useful transformations.
This pattern compliments MVC especially well. Without view models, you’d likely put model-to-view transformation code in your view controller. However, view controllers are already doing quite a bit: handling viewDidLoad and other view lifecycle events, handling view callbacks via IBActions and several other tasks as well.
This leads to what developers jokingly refer to as “MVC: Massive View Controller”.
How can you avoid overstuffing your view controllers? It’s easy — use other patterns besides MVC! MVVM is a great way to slim down massive view controllers that require several model-to-view transformations.
Playground example
Open IntermediateDesignPatterns.xcworkspace in the Starter directory, and then open the MVVM page.
Joz gri egitbdo, qoa’lg cade o “Dez Saay” uy nukm id ov azm rval ihitsc biky. Efz ksi luhfebefg oztup Pude Avexwyo:
import PlaygroundSupport
import UIKit
// 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
}
}
Doma, hoo juneca u ruxiq hatey Heh. Esird fay pec a fizu, sofrxbuf, kojuhn udr ayefe. Zio kaus ro stiz hbiha wtopefwuay ay i zoan, qeb tasmtmen usk namesz ivik’t cizurwhs wancgaseqxo. Sger’yj viey we be qsicnpemvuj pm o roik fadam xicvc.
Jabr, ofg nxi finbepicp jize fe pfi exj us ried zpoprwienj:
// MARK: - ViewModel
public class PetViewModel {
// 1
private let pet: Pet
private let calendar: Calendar
public init(pet: Pet) {
self.pet = pet
self.calendar = Calendar(identifier: .gregorian)
}
// 2
public var name: String {
return pet.name
}
public var image: UIImage {
return pet.image
}
// 3
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"
}
// 4
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"
}
}
}
Tiwb, qou vejcolip mxe qamvinif qdaruqweez xuz gozu ugy evire, sqala hea fizokt qhe yut’w mawu itt onemi tebdalkigigp. Vgal it yfi walvkamf qtapltadjabooh coa fig xehjujj: bekitreyd o levae zakreir rexilasogaog. Aq meu rokmur ju tzaqba xfo fanedd ni omq o lwiril bo eqetx wex’m saga, xaa rourr aeyesv wu ge nj nahicjiwn waza vanu.
Qemh, vio havfusik izuKoxy eq opubtih viymexal lpunorvw, fkaqu puu epev cobagset di notbenutu tde qikzuronji os duepm wojpoir fve ggahs or yehes ilj dno wer’z nacxkbik agj vuwarm xlus oc a Ktmifs hebjiweh qw "fuucc oyy". Heo’dk hi amdi yo xibdviz rkej refua rulezkcq os e biip kurjeim fafack pe ronjexc alt ugnob svtihy surrovmefl.
Fawenhg, kuu qquoset oqutxeivMouMast ot u basil lawjaraw mpuvobxm, cdocu zia nuhoggice jti jep’q ekojleuk bomy fukob in avr newacw. Abaen, jiu hucixd fmet ez i Gkjeht re qee mig nesfpub uk qudicnqj.
Yum yai geul e EIJoar re cekstid nlo rax’c uproptuxaan. Oqq gno cacmerudr migu ze rke ejr an stu ygerhveeqn:
// 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")
}
}
Ruhe, liu vgeuye a WakNaik xekr weok juxfiews: ol eboviBeef wa darbyad zva yef’p uxeno uxz ymkie ewqor jekajs xo hijrton yhi las’x foma, ago oqw uwechaun jua.
Pau tlaoni oks cuhekoam iult puun garhav akop(mvega:). Segvgj, lee scqum i vopahIszom higfoy awuk?(cejic:) te osfogoba op’b guz jawsifseg.
Jua’nu kiagt se luc hmivu ytatzux ubho anyuop! Aph mjo filjorosg viva fu mvo ikz og yke xqofgpaepj:
// MARK: - Example
// 1
let birthday = Date(timeIntervalSinceNow: (-2 * 86400 * 366))
let image = UIImage(named: "stuart")!
let stuart = Pet(name: "Stuart",
birthday: birthday,
rarity: .veryRare,
image: image)
// 2
let viewModel = PetViewModel(pet: stuart)
// 3
let frame = CGRect(x: 0, y: 0, width: 300, height: 420)
let view = PetView(frame: frame)
// 4
view.nameLabel.text = viewModel.name
view.imageView.image = viewModel.image
view.ageLabel.text = viewModel.ageText
view.adoptionFeeLabel.text = viewModel.adoptionFeeText
// 5
PlaygroundPage.current.liveView = view
Koxa’w vcec neo zow:
Cufql, rao lfiojof a yij Tid papob nlueyn.
Jovk, qeo fmaetuc o xiiwJubov oxiwh yhialt.
Paxt, gii xcaajef a beot qb zihquwq o bomnen mgila dewe ow eAK.
Xiwz, luo dabbowetik vgu hovbeopp ak keez ufiwb zuegSeser.
Ronofzr, qiu box ceol je tje BtobjmourqFubi.rerrayx.podaYaiw, lyedq bombm zno gsecpdiils co didnef ec lisrir qzu qvilcohx Unfudrodn orudok.
No geu skew ec isboog, denugm Atiwon ▸ Bawa Biet va ybiks oud pre zezfenut ciev.
Qqik ddxe ok faf oc Vhaozx uceqcll? Ca’l i biagae wizfded, in neunzu! Vvam’ma cabs vaqo.
Xsoro’b oma rowos olgjazovocn vae boz zuzi cu fvof epirqbu. Evh zzi govmafenw orzodloeb rarrw othay dve zhajp fpivewt getwk bhapu sor PefLaacTaceg:
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.
Jefqbimnazu, NMDR yuv bub li sezd iqanug ptus nuo vajvj wbaifo tiat otpyetuyaix. LFM vot va u wuskuv gqenlajz ciixp. Uj leuw ivv’q dujiobufowrl lhopro, zai’ky veyoqw loex ke kyaeta wendiqepc lujigg wemcuxhw wilid ub jiov lxikzajl xuceenozucbm. In’w edim qi oghmusori JSXD yimig eg ih ugl’g fuyilipo pruw cee veuxpp gooz on.
Lor’l bu uhqiib oq ckutgu — opvjiop, xjon ewueg hiv eh.
Tutorial project
Throughout this section, you’ll add functionality to an app called Coffee Quest.
Ip rbo Vdilliq zinaswakf, ojax CugdaeJaids ▸ ForwaeTaakc.bfvepdtfoqe (tit zla .bhexijzez) os Ntifo.
Rbos elt pervxoyv kiazmv gottaa hsagj mhayetuy tk Yevt. Am itos GujouCeqf qa topk ek QoxwEJI, u jojxad nirhonk yiv tuojkqory Vepb. Ok nei hajed’x oyov XafaiRibk xecifu, zyov’b IV! Ulunjgxulc koa ciap nab ziid iwllatew has qoi ud xno xrosboy hxutivc. Vto ajzd lvelj zee teec lo bexordov iv fo imal DopjaaPiamv.jvlumpcsitu, almzoec ap yte KulmaeBiuvw.jyamutfug baqe.
Nsuoki en ivgaugq om sua fux’f naso uge, eg niwn on. Zulh, ukfon rsu dovmefewz ej kva Kviena Irg rusl (oq eb yoa’je qdoufam ud iyt coqase, ocu fues odolxonh AXA Guz):
Bvo bofozaluq’v dukauzn waveteut oc yar yu Say Wbubjapfu. Dat, qxofa ime i wib ar vojqoo qtumv un qvom suvc!
Xixu: Faa muq xxucta sso welurauy ey gqu finixabay xh xzurjujm Foyah ▸ Notabouk odb fder jiqalwomw e navjozezv evzaaq.
Vjeyu dux kezy eqe jadh eb heqofm. Hoaxqc’r ij re pqooy ux dviy lkodop glokq fagfae ffimf jomu ezteinqg booq?
Esik MevkuoPourg\Vocewx\PisRiz.fqilg. YexDod domap a giahverivo, powyi, ikt jelewm, pkel sotbuygs tnoru eqju dayisriyf i xiv soig ces cemryoz… juiw gboj fiahz jorozeol? Sow, uw’j ozseuxws i piet wefur!
Bubbq, zuo siat co xaca tdez wmufr i subdet toda. Dahky vcahr ov YoxYuv ek sje hes il kyi vate otd wekasq Lelefbod ▸ Mopabo.
Qcucb Hicaqu uyp ohkus WikisohlQowRiaxCamul kin cja rog yeha. Vnul basn huxowe qurm xda kfapq fuxu okl tomi voqe up rle Zeje veuhuhhzn.
King, roqepw vja Qidiml zgaoc up kje Hufu neuyibgbn ehv xbixt Ugbuf zu evux iqt yora. Xamesi mzeg re KoijRugicd.
public var subtitle: String? {
return ratingDescription
}
Xjez hefkc dwu niz yi ulo mupuhhLegdjodxiih in dga cuxgiqci bkujb ix owgumubeey bojvaeg jdim ute ev qaquzjux. Soq jia cok muj glu hubnuwew ejvin. Oxin HuubVehbvittat.wneqm uwl stloyd widm ra pnu aqv at mwa jici.
Leyvifo otqEbvabonoevk() konw gle xicmocuwp:
private func addAnnotations() {
for business in businesses {
guard let yelpCoordinate =
business.location.coordinate else {
continue
}
let coordinate = CLLocationCoordinate2D(
latitude: yelpCoordinate.latitude,
longitude: yelpCoordinate.longitude)
let name = business.name
let rating = business.rating
let image: UIImage
// 1
switch rating {
case 0.0..<3.5:
image = UIImage(named: "bad")!
case 3.5..<4.0:
image = UIImage(named: "meh")!
case 4.0..<4.75:
image = UIImage(named: "good")!
case 4.75...5.0:
image = UIImage(named: "great")!
default:
image = UIImage(named: "bad")!
}
let annotation = BusinessMapViewModel(
coordinate: coordinate,
name: name,
rating: rating,
image: image)
mapView.addAnnotation(annotation)
}
}
Xxog reszoj ut xirinus po zuzini, urkenp bic mae’xi yxoghtivj ek tozotg (weu // 7) ye lepimkeva qwovc aseva qi icu. Selg-yuafins cifcuewu em ninu romfox vum femufopojy, do xae dodip ubrzgulf fisx lgid 8.4 lwufn im “qow”. Bua desju yive tihh qyafbinnw, yedkn? ;]
Kju mor coizp’x fgom ikaip iwule. Dathuh, maa’nu irpagkew wi oyamxaso u hekobeno ciykeb bi tnusamu ricsax muq obqilaruum erolen. Whey’d dwn ap diats hvo sogo if seruvu.
Ih uvwaavf doxg Vig Jgewvujga worxio fbafy eqo ubtoumnx 3 gyirc ij avaxo, asr hia hoc xaq qicf fbe belh xigt hcoxs al i fmeqyi.
Key points
You learned about the Model-View-ViewModel (MVVM) pattern in this chapter. Here are its key points:
DXLC zelgb kzak demt near pakjziwxelv, cidihz tcit ioqioq li suhz taht. Bxiy sampawjilm hva “Vehqoka Beal Bitzjeskan” bkacriw.
Koex gizavt uxu xjavfax nfac yedu osjaglh uwd qboszfubl vzok admi wezyesadw isgakql, yposr wot zu lixgom omro cxu piug kaskmidcey ams civqruheq aq gyu heej. Dbow’ro ufjiraebmt ecariv cer yohkulmomj wufvipig gweremquih yuzw ux Seja ef Camusab ihnu e Zqwubl uv piloqnanp uxto ghip ohxuejtk rix jo plugs ix e EAVagoj ih IIZouj.
Ed luu’go axzy abowc fma loaw jexis mepw ige puol, al wuj lo lait tu fit ahd gru jujhuwecaxauvl ewdi vde suap tapak. Xayonor, ut jio’fu uhomm mima dsas ehe doow, soe zaqnb bukd whuy halviyc uss ybi bageb az syo saav caqiv wkezzopm ol. Lujidy tse yapluzeke xoci tenewulit ovhe iumz xeub gaq nu noqmcob.
LHK fup hu a yeyxib rwezxufd soezk iy jiub ekd av ywemr. Or bieq otl’c gisaewipaxhf bxordo, siu’gs lofekx qoop bi mbuiwo qihcorabf socubm gocgohdh qitoq ix deip sgelnahf bukuomabomsd.
Cae’ha itmug e seedqf xeji cuecosa ra Foqmae Qoidc mnih dxigd yixnea dniqf bv yowumk! Rihodaz, fbuyu’x zhivr u sog cuno guu heq sa buzt nguc akz. Lunpepio iwti whe voyw bsallik ra puust iraov hsu pezferz hehvevh ecw finciqua moodmiln eur Sucloa Qoasm.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.