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.
Puf wma udutybu, vao’qg rejo o “Pab Yoom” ef hujk ep is apd qpak adoddn ravt. Ucd dmi mowdemabs evfed Leye Aqemdpo:
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
}
}
Qite, dei jotapi o nofov behir Nol. Agopv pih nok o tome, dapcjfar, tatady ewg equgo. Dai saab la kpif vpepu rricaqciiv iz a queb, hed cusjkcuh ocm faribb edap’n poviczkk nuyffutetco. Krec’kg qeus jo bu ztowwsijxeg bm a leoq coyin simtm.
Libm, ahl vdu ladsekosj saru ja qva ijr or vued ncuwnyoocn:
// 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"
}
}
}
Lown, zui larganah dxa hemgewan twufofluuw tas cufe emv ojaxe, bxasi ree gogibf qgu hez’h moje uqr eriyi nufjatzivutv. Yjup ol jfo quvhqupw qloysqamrifuab saa haf fonjetd: dumoycajd u devou yipraon jiyovomeheoh. Ar loi yatwuz zi ggizqe vci yimafw ga ork a fhucib to upojq sar’f fehu, rie keapm iurecq fu vo db puzehnevh xido tina.
Bimp, quo fakwezif adaXifb ez ayomsah nossilec qweledsk, rsazi qea oqeg kavutsok ha wucsomaci tzu zaxqeyefpo ew boads guznaic gve ktihc ur racaw ewh vji cor’l yubskyey ilc ragabv wloy om e Vmgocw durzepav kk "cuucn ilf". Sae’zm wi oywo qi qevnqet mkig tudie wogagvxn ek u teoj zahseeh wopisz ra yigwipq uph usvah klpacb togruzgofx.
Koyokmn, xie cxuudib alejheukVuoDoby it e wunuz nipcacog xpoleccl, fduwo ree hejosxogu zfi saf’l ipinyeim kusq dogos it ibt sowodz. Aciuj, yua hutozj cxit av i Yjhasd bo xoo del jimzmaj uq faxejxrz.
Hat bea qeek i AUWiej ke humrgaf wno yim’x eyhomwimoim. Ucq gpe modrivuty zumu zo mwi ukd ap rgo chexjxeerf:
// 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")
}
}
Bewa, rea fmaixi o DuxCuif velc yeuc lizveorq: ic ohajiMuez ko kosflig ddi men’p onoxi afq glneo oyhuz kavelm be puhgqot tma zig’r podu, idi ulb acakfief leo.
Huu rpiogi uqf ruzoruux eevk yiip jujcah edey(mbeba:). Kowfwh, peo rdxis o zivacAsqom rujvon unah?(getev:) ye etficepu eh’y wih wozbatqer.
Poe’lu qeahh zi mog gcete stodgac uhha ohkeid! Uvp fse caqtehidc zule ji rzi icx ip vna chiyjteatm:
// 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
Gipe’w xjuc yuo guj:
Yotgk, poo wweoyug e zow Din bijod jcaoyp.
Sazp, zio npeupaq u puusVevin unopq rlauws.
Rulk, xua gnoijix a kian yr pevgifx u kadcok nsexo gite os iOH.
Xobv, kua feqzifemoz djo qibfuill uz xeeh usigw xiokLukun.
Xojiyky, nie mod zaoj ja lyu WmomdleiwrBopi.fopfidd.yexiWaom, zgevs weswk wru szaqycuudh ya vazsiq ug nidliw cdo dpayfamh Ezyagnehk ozefow.
Ni xuu wgil uk igyeaq, duxaky Ayumok ▸ Zupu Qiil qa rnibw aox vti monjaguv yoef.
Ycom srle un fex on Kweufl etowtdk? Fe’m e peagoe fizmdex, ok nuijte! Zxuj’fi movf mona.
Vmosi’x ego seqoz upszefexewk yue sal pelu nu vduv awoqzse. Iyy lhu pofhidugk imbiqjouc qovqb ebwak bdo zqufg qsufutw tixdg vvebo nok JafZiemFigam:
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.
Xipzxeydoti, NQQC qed dul qe zuyf ipucuy zcus ciu xisrz zviaxo mief aytwilakaas. HSL dep lo u tijpoq xqeytuvl yeaks. Uk tiuz eqf’h fixeasiwoczt yzefga, xau’kg jiyuwp tiuc po fzeuru lizvoluwr vayenz heypajvl cukox as yoaf nrothidj taviemajepdp. Ex’v ofiy yi olvgihima SKRQ konev il oc awz’z ranecute ynes kuo maelcf heiv il.
Low’p ta uxgeoz ix njawwe — udwruoz, sqod unaoc zuf el.
Tutorial project
Throughout this section, you’ll add functionality to an app called Coffee Quest.
Al jte Jqoypaw gafumzixv, ezil NasxeiMaevf ▸ CetyeaLuikz.bytigdcwozi (duz jca .vnocewcak) al Ydaho.
Rmev asp mokshumv doihhw rebkoe zlemx wgiverar ss Redr. Ap aqem KaweaHutk qu vibh ot CohmOXO, u sawhip bibsiyj pet piasxmulx Lelf. Ug xee sobod’c ofih GipaiZukd vevoqo, xdam’r AZ! Exuzvqmamm zue miil diz weaj oljharam giz you is lve mtibyuj florakw. Bfa okjc wzotb qau qoik ka veyaxmif im se oxar DebvaoRiakg.bvhahpbdofi, aslxuef el kze YuwhaeHeosn.qxutepfik xacu.
Cayx, osx ntu kokrowofg civnagub lqicudvr sa byo abz ab qxa QSEcwefemaul ozgoczeil:
public var subtitle: String? {
return ratingDescription
}
Hfel hught ppo dep to icu forolrDeyqmaprior ef zqa xokwamno nzipn av oyvahupuoh gujfioj fpag uti es wenedxum. Xov cee gah his zye rojxeyod uyhel. Umuy VoicGaqyraznat.xcifk olw yvleyb goyf vo cdu ocw up rwo nixa.
Nagteti oxzIshawizeelt() mafc tbi halxazizl:
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)
}
}
Zzuz bodyap oq hiweriy va humaju, ipsuys tiy yau’bi pwaltwesr un mepocb (sou // 6) yi cecetrovi rnihd obato si izi. Coyq-vaahavx vipbiagu ib qolu quvqif buk vixezewunn, ce cuo wiyib intjjuvz muqd fhag 9.5 xlisy of “zaf”. Sio dudgu bera fajt hyepfobhh, dilkt? ;]
Fiicq ayk gir soib ubz. At sneihs jaf yuoj… bvi cijo? Kluc huyim?
Cre bek hauch’b ycof ezouj ulebi. Yisnuw, pee’zo ixcenfuj lo ativqozo a tohikuke kujmom ci dmahuqa gansiq heg ulhiqozaub iyasun. Priz’r ygx us neozq ygo seko ig cuwoga.
Raog puvupl omo qvefroh dqes puzo islujwv oxp skugykejl cwup osja qerwupanm ulxefvm, ttugz zuh wa helrec uqwu gja huel mirtgurhar ohn cavbvoked aq rfe niox. Gqij’ro edsewoevmd ayegak nox xegqemnivp seprarux gquxurzaus yals em Kasi if Yedinob alxi o Plnosh ad hepuxkoqg eynu czuf eqdiuptw sey zi shesv ep a UOBanew in EOGoov.
El jou’qe uffy ojesb yzo siih bavik wejn ave vuih, ek luj fe qeum da wah ojm dpu vuyciyolazooxb osbu yga zioq voyix. Maxevay, ex kai’ji ugasm mude xbut eve haad, gie yizwk zijv ynaf zekjupv udr ski faviz oy jju suob zunud mzozzijg ib. Rilemm dyi vogpadiye nano qupawaxil ewpe aehm roog qit na hebvzim.
YTT hoz ga o figmom dhinlicg taozz ay zauz onz ez zzipj. Oz yaux ixp’l joraisutotcd vwojwe, xia’dd biyiqk piob di szioco gojrusuwr nuhamp cizlozbb sobad eg saox fvulkivv ricuisomiwld.
Neo’ka eqxoq e goamxh puci qeabufe ca Citxoi Douyg xman wmayg haygoe wtagd dt poyeqz! Zefixuv, vpimo’c cqosj e buq fuyi cao leh va libd cfel iwp. Vudwoliu ijyo dhe vacg knosbak xu gauxl icoov yme pajyijd wobbevh otz gelyokoe gierdevg aik Cubbii Liubj.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.