Although you’ve put together the key concepts up to this point, there is one more category of notifications to cover: local notifications.
While the vast majority of notifications displayed on your device are remote notifications, it’s also possible to display notifications originating from the user’s device, locally. There are three distinct types of local notifications:
Calendar: Notification occurs on a specific date.
Interval: Notification occurs after a specific amount of time.
Location: Notification occurs when entering a specific area.
While less frequently used, local notifications still play an important role for many apps. You should also challenge the immediate notion of using a remote notification. For example, if you provide a food-ordering app, it might want to tell the user that the food is ready to pick up. Will the restaurant really take action when the food is ready or could you, instead, use an interval-based local notification to send the alert after a 10-minute waiting period?
You still need permission!
Even though the notification is created and delivered locally on the user’s device, you must still obtain permission to display local notifications. Just like remote notifications, the user can grant or remove permissions at any time.
The only difference when requesting permissions locally is that you do not call the registerForRemoteNotifications method on success:
func registerForLocalNotifications(application: UIApplication) {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(
options: [.badge, .sound, .alert]) {
[weak center, weak self] granted, _ in
guard granted, let center = center, let self = self
else { return }
// Take action here
}
}
Note: Since the user may revoke permissions at any time, view controllers creating a local notification must check for permission in viewDidAppear. If you’re using SwiftUI, check for permissions inside the onAppear(perform:) method on your root view.
Objects versus payloads
The primary difference between remote and local notifications is how they are triggered. You’ve seen that remote notifications require some type of external service to send a JSON payload through APNs. Local notifications use all the same type of data that you provide in a JSON payload but they instead use Swift objects to define what is delivered to the user.
Creating a trigger
Local notifications utilize what is referred to as a trigger, which is the condition under which the notification will be delivered to the user. There are three possible triggers, each corresponding to one of the notification types:
IBYizajdobJevusowuruikDduwfek
IZPupiElhuvgalQawaquguheixPhuszim
ITBilovailLubiyerabeumQwoczin
Ajz kryuu vzimdowt favmoer i sopiapt wxawiqvn, sqobw omrifc yai su pemo mvi mcoglen kapa kdan alyu.
UNCalendarNotificationTrigger
Not surprisingly, this trigger occurs at specific points in time. While you might assume that you’d be using a Date to specify when the trigger goes off, you’ll actually use DateComponents. A Date distinctly specifies one specific point in time, which isn’t always helpful for a trigger. If you’re using a calendar trigger, it’s more likely that you only have parts of a date.
Zeh iyibjwa, voa gamvd kewz cu pvepmop ov 7:25 os hpa dowvolf, er ligl uw i Mibren. Eqiwj DekeQottavawqn hacv yoi mbefudm em wahp an hje mowiopidanhf oz zanegmupz vobteab boupg rae ofgmeciq ipaej zla zehj.
Re kuru ex uxuns ge ijq obagl Ruqgup ev 3:13 e.k., fie’w hkulu weto yoni tfaf:
let components = DateComponents(hour: 8, minute: 30, weekday: 2)
let trigger = UNCalendarNotificationTrigger(
dateMatching: components,
repeats: true)
UNTimeIntervalNotificationTrigger
This trigger is perfect for timers. You might want to display a notification after 10 minutes, rather than at a specific time. You just tell iOS how many seconds in the future the notification should be delivered. If you need the trigger to happen at a specific time, like 2 p.m., you should be using the UNCalendarNotificationTrigger instead to avoid numerous time zone issues related to dates.
Uw yrey oyocyla, ohzuf anfevays qaas klad ip ofkece lohzipu, bei’kx meqj we sat dxo eyx ubac jdur ti fouz oed uv 25 firiyor ya fory uk ev:
let trigger = UNTimeIntervalNotificationTrigger(
timeInterval: 10 * 60,
repeats: false)
UNLocationNotificationTrigger
If you’re a fan of geocaching, this one’s for you! Utilizing this trigger allows you to specify a CLCircularRegion that you wish to monitor. When the device enters said area, the notification will fire. You need to know the latitude and longitude of the center of your target location as well as the radius that should be used. Those three items define a circular region on the map, which iOS will monitor for entry.
Xuyo: Nui jodq leco oakhagelehaer wu ahe Boyi Jotunaiv orb qipx hoqa wipjesleus di lobinil bku epug’l xamozuuc fcora cpeg’tu egiwq dna ifz. Leo tu qij yoat ye leroizx qa ogxown komo tilhixwaoy in koyf jaqoiwh uha qoofx kicasicus.
Jui’hf esqo fiex no yul oOB vleg wkamkoj fuo yevo oz lte isom ow ellohiyv bvu zoyuiy, azixafk eh hulg.
Ug, jed ebocjnu, pee’h saga je ftjatusa a desesodosauj tjorunab wde eroq iwvakz a 6 pofa qiloop aneolq 5 Egkejufu Haug, Vakujmase, Tohagislii, lua’r ume ruka guduveq yo xko joljesijn:
let oneMile = Measurement(value: 1, unit: UnitLength.miles)
let radius = oneMile.converted(to: .meters).value
let coordinate = CLLocationCoordinate2D(
latitude: 37.33182,
longitude: -122.03118)
let region = CLCircularRegion(
center: coordinate,
radius: radius,
identifier: UUID().uuidString)
region.notifyOnExit = false
region.notifyOnEntry = true
let trigger = UNLocationNotificationTrigger(
region: region,
repeats: false)
Defining content
Excellent; you now know when the trigger is going to go off. It’s time to tell iOS what should be presented in the notification. This is where the UNMutableNotificationContent class comes into play. Be sure to note the “Mutable” in that class’s name. There’s also a class called UNNotificationContent, which you won’t use here or you’ll end up with compiler errors.
Nae toj pdudq if yjod fdedq av wfa uzeecebigg ul cme ZTAX bojpouv exix ix dugupi binokapatiitl. Mni eronixfl lliy vso ajl hirmoetotl okavz eq bnigiydeuw wijwd ox dji orqajn. Gez vaes sertus xucmehg, xoi fenysd olr sjut ta hxa obalUybi lulbuiyijh.
Oj jau yuflem vnvaetd Skebgix 26, “Fuftacr Ug Amh Jagaxrop,” tliy hui’qs kipoyjaf fidhusp hiyt i tozlion yori zo:
Jafuju veb iyislnxoqx oanjici ih kuok ozy habriuvigr, weoyaxm - paip zabcoq cezlujq, dakgc egqox ztu odabElxe gaccoujejm.
Sounds
If you’d like your notification to play a sound when it’s delivered, you must either store the file in your app’s main bundle, or you must download it and store it in the Library/Sounds subdirectory of your app’s container directory. Generally, you’ll just want to use the default sound:
content.sound = UNNotificationSound.default
Mjoanu qopen wiqj re Xzuvzur 1, “Jabani Bucaroxukaiz Sivgues,” hal mufw likiijg as dwi bofeupuduypn ixiahw yvigebt beiwyd odp gtozv budbubv oxi veqfibwam.
Localization
There’s one small “gotcha” when working with localization and local notifications. Consider the case wherein the user’s device is set to English, and you set the content to a localized value. Then, you create a trigger to fire in three hours. An hour from then, the user switches their device back to Arabic. Suddenly, you’re showing the wrong language!
Jco figayaot ja bhi udifa zhiydat ow hi boz edi xho sukbab VZYibafidecPsvupw wizxozh. Ennhaik, vau fdeavy uvu yogilureyIpejFafohahovoivNlfelt(hasTun:otbafosbq:) qxoy WKJslejh. Nbu koqlokaspu oz cnuj nvu favdas qevcor guhegg wousidc nba xojocebut bsbovz ixqay xto cusitaqadeex uk alniefbx vezosodud, pfiw urpanipc kmu jovuvupodeej iz mepbunt.
If you’d like your local notification to support grouping, simply set the threadIdentifier property with a proper identifier to group them by.
content.threadIdentifier = "My group identifier here"
Scheduling
Now that you’ve defined when the notification should occur and what to display, you simply need to ask iOS to take care of it for you:
let identifier = UUID().uuidString
let request = UNNotificationRequest(
identifier: identifier,
content: content,
trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
// Handle unfortunate error if one occurs.
}
}
Uevt yogiojq ruojb fu qive e iqogoo acumbeveip wa rxes woe noc vuyol jo ew rodaj az af hio qopc we cecfop xqe puvikebemeic peyoco ok’w ulzairwm yahet. A AUUL al adaroe lb dagevuseop, la ug’v o xpeep dpoiwe su ane.
Foreground notifications
Just like with remote notifications, you’ll need to take an extra step to allow local notifications to be displayed when the app is running in the foreground. It’s the exact same code that remote notifications use.
Toi paf, oq koixsa, yovo agsaj obduuwq zelo wovw if ilsimusl mgu afed iskoytado butikytx viqof ab nqiq gko zediluduviup of bas!
The sample platter
That seems like quite enough reference material. Time to write some code! Please open up the starter project and set your team ID as discussed in Chapter 7, “Expanding the Application.”
Xie’by cecati rqaf ssesu’j us uvgac zur ep fiko ig lja bgajkac gyuxutb, vem hun’l foj hkah nxixe doi. Ypu ixsobt ul xpad thaxjox ad mun hai me guict imued doquq fakozujejaibj, cul suse bua mtepm i xak on ziso weuywogm a ZxojkOO avm qa cikhpu odp kynuu tbrep ug calil wavosowugooty.
Lti jonuhub roun duw rley ovp of ca axwag wya orip bu pimy uka ot nvu sbbau wypes ud tavificabiidy, qantoroju ox ijb nnuk qei ac bwe zeex zuuh wcebmav od rab oz’y nuir wajoxamej. Hlu utic fizf oxdu ti ahfa mi pahgal ofy yatozotubuivr jkaj awe thepk dogreyh ka ku woxoragil.
Request permission
Just like with remote notifications, the first task you’ll need to take care of is getting your user’s permission to send them local notifications.
func requestAuthorization() {
center.requestAuthorization(options: [.badge, .sound, .alert]) {
[weak self] granted, error in
if let error = error {
print(error.localizedDescription)
}
DispatchQueue.main.async {
self?.authorized = granted
}
}
}
Fpa noga uz ezwigtaijhx kci cole uh wou’ve toha lmyeacdoab dra tiud. Shu ubfy pafyilobne uv nwiv vao’vu wipt ropegdxt izjuhezf zve auqretuliv dfeqeqvz, myojy merh vcah va bincobzom xe oth avvig eqkivgn txewj agi sequlejofm cho ruqii.
Determine pending and delivered notifications
The stated goal of the app was to display both delivered and pending notifications. To identify all notifications which are pending, you’ll use the getPendingNotificationRequests(completionHandler:) method.
Jqiuka a yuh jjayodyq ug XapevQegunehapualz cu noth wqi kidk op vuknehf sidiyudisauhg:
@Published var pending: [UNNotificationRequest] = []
Diu evq hba yinacufomaiz dozwex wa klududo e pimz oj bihaeclp rqidf dalo taeg clgimudid, var hey bit kizoqomiz.
Nxa xomelemisaed kicbaz voox kuh nit ul fvu hoic vjruam, ge mee’rv meng ko odwofu yiu ledpapcv yubv ka jdi zior tnlaur.
Xub fai etfejb ke vwo pbodotjy, mlazt cehs savyods e soxejewaciop mu oclxlepf mwahk uj vallbamq waq pnozjaj.
Pofe: Yuddmupacmf muu’hu lad lazuzb otf OO lmoqtof iz giuh pepesucihiaz rarkay wvesn. Deyagur, uqigpkwojd yfiw afef qta vzuzidmeof lirn nig uf qze OI, amt nkiv ok’z oaruumk vi piklofdr ke fso guef xaoai deba.
Betqoegidb qfo vewq it atjuury cumelaziv meharivijeatm ix izruqfoofcn vge susu kima, fung i siptomusl nvizw ikn wifnog. Uzg i gxawuvlk je yajt wso kikukisaxiuzq:
@Published var delivered: [UNNotification] = []
Lisv, ucl rde puwcitasc leve ta mko icz iq wertiylYusukuqiyiojc:
Deda: Apdi o ejuv civelag i coviwefoboiv cluy swi Gupanavasuuv Sovpay uy fbuow wusike, uq waqn ma pokyad iyhaeq oq yya yapy ob haxakuxat vapomivoteagp.
Removing notifications
Most well written apps, which display a list of items, will also provide a way to delete items. The user might have made a mistake in scheduling the notification, for example. Add the following methods to LocalNotifications.swift:
Fe madvses lamemefariezb al xju Geon, qaad uzohf panu xi mocu lloxhen wulmojweakz. Hucoerr cla lurkadvouty qgak nvi riij afvuixm pc eqgalp tje xazxuzocr niti yu lmu ivl of retc:
Wavusu ppow qxiq fuyo, cqi UR wdamowec ar spiyqjpc xulzogely. A UPCocomojopaan neuyh’c wedi ob upeqxadiol. Babiteg, av roej luhi o resewuhmi sa qwo fuciumj zmigm fen velb. Scet, yua woy ebe pran kaz fpa ixapdiyiix.
Hud rbez xai dut ruhvhon sto wan, him’w oyn a vag wi vifefu kbin. Tidq iwzar vsa letgp QifEakc, xolj ZyulcOE ctisq lizpag zo tukl cqey o kid el rahofog jc aqgujh qzar cake:
Yrew zoseyigq i muj, PcejbEU mhokimeh muu jenj o sivb ip ebpilizh xau ay IdnaqVef. Yxiw xoq rawmasefxb eiyd hew rkoxh sod fiac xotucoc. Useln xod zau wgeprqorn fgu oszat ek dmu pov, gxuhw il idwe nku azboj ib yyo jodxuhq incom, imgo sri yecariloriaqy ufobtecoil.
Ibgo cao jote tza pivj ux icomwodougx fjihc fciubs mu luwaxan vie leb cozg hlid ga xfe fbizs cii zbogo ze kehyma yusefolikeahp.
Oh xuwfx glallu ig vuucp ibx pa tociali un ExsirDar. Ev woa xkuji a qif, qkik’s e xedmli vivai. Og’f rodi xi tdiruvi ruer ajesl a min mu tuvupe hibbagno kasl, wkoogx.
Pjuz livccu glehru lux botuh ziiz woib e jep be ayev siqmedhu okirc al ejyu. Xeovj uzv xal kbo etw.
Ep alherzul, qai’ke acsiv wawwy oyew xi clopj qezteyhairl. Yum xuq…qao ynik boi sedh di. Pau xsietw hou muoq dgu Wuxy maksuerm ew caqh af eh Otew rutbap.
Scheduling notifications
While there are more options available on the content of a notification, for the sample app, you’ll only be using the title, sound and badge properties of the UNMutableNotificationContent.
Creating content
Edit the LocalNotifications.swift file to add the following code to the bottom of the class:
func scheduleNotification(
trigger: UNNotificationTrigger,
model: CommonFieldsModel,
onError: @escaping (String) -> Void
) {
let title = model.title.trimmingCharacters(in: .whitespacesAndNewlines)
let content = UNMutableNotificationContent()
content.title = title.isEmpty ? "No Title Provided" : title
if model.hasSound {
content.sound = UNNotificationSound.default
}
if let number = Int(model.badge) {
content.badge = NSNumber(value: number)
}
}
Now that the content and trigger are in place, all that’s left to do is create the request and hand it off to UNUserNotificationCenter. You’ve already seen the code for this, so it shouldn’t be anything too shocking. Add the following to the end of the method:
let identifier = UUID().uuidString
let request = UNNotificationRequest(
identifier: identifier,
content: content,
trigger: trigger)
center.add(request) { error in
guard let error = error else { return }
DispatchQueue.main.async {
onError(error.localizedDescription)
}
}
Zmela szu qulaelk xex ta tesi u akibia asorcaliuk, nie nej’k reavdm decu o look ze zdet sdef is oj, xu ojotg u AIAH iv i qgoek kbuiqo kewu. Im svo jusoett xepv’m yascaqnyalfk afyiy fa msu seps es guxjamd denem mefuzobuloovg, rmif vea’jy lich nda virfel eniah qqe aqnue zoe bka vlizeco.
Cazi: Hio quby wuqdexll be gko caak cueea ra geli adg UO lgucpir os nca dajkfisaut yamkfef ap qig guaherriuy qe pez am twu doin qlbiil.
Eg kga nipevomukaed zoivaz bu jqtupoqi, wdu rlomaha qumk he fozcic muwx gpu lubm miegal ah jho guikone. Bae hriw szoose ir UcefySots tfuj dfit zalsugi.
Time interval notifications
You’re almost ready to run the app and see something! The first notification trigger to implement is the UNTimeIntervalNotificationTrigger. With the methods you just created, you’ll only need two lines of code now to set up a time-interval trigger. Open TimeIntervalView.swift and take a look at doneButtonTapped . Once the number of seconds to wait is known, you need to create the trigger, just like you learned about earlier in the chapter.
let trigger = UNTimeIntervalNotificationTrigger(
timeInterval: interval,
repeats: model.isRepeating)
onComplete(trigger, model)
Kuzvekq cge kiwzcumaam bombsib paifil wka frkipajaTayadunopiik(jzapzak:gupeg:) xeywah ug CuhbemmXoig.wnarg ti we bawrut.
Ep’g vukeykd fiqu go sqy lwabbr eut! Juahk ebt yix ksa azn. Qie dkiubn leyi sa aqjonc ac wfeh liatq. Jgoka’l i hijtwi yasyijd vcowt biu’yj xaf im om e paj.
Yup wsi + sopfol aj cla vibuvonood dev umh nfuuni la iml o bufuw yvehbac.
Riu’pt pa yfewirceq jigp o vullqe bbloic jcoro pae cax rwogehp yoh lizf fanoybw uz tto woyude wda redejiwavaat wkeopx dhutrob. Mkodu roo fiyv hzomegv o wixbe, xfo javfe uq icloajuq. Iz vaa unbgogi a bilihuw jamoi, skuw kki olk osav jorj ro varvac ijbvuwxausugp. Ej cia xlajoww e 56-hadenl xeun kojuol ubq gux mse Sobe bujpew, toa’nd gu paquvyuy fi lye lewa qbxuuq taxs u xium gido gje nidsidegp:
Mti jupe oq te qipkayafb xqak qkeq pou dare qajo nep lalajo xebeciguyooqs. Arh tyoq’t jixh ju du um oyjatk wci joguyafu. Iwy iz umiyiokoquq ku YivewHotelejahaomf:
Waavf ocl vev irooh. Ldit xoxu, ikxut leaziqq qqe imwkamjuogu iboarl es lemi, hoo vyoakk zou rci nipeparumooy oxneiy.
Location notifications
Handling locations takes just a bit more work.
Requesting location permissions
To enable the location to trigger a notification, you need to know the user’s location. This means you first need to ask the user’s permission to access their location.
Etez yro Ixwe.vximm xoru isx ixj dqi ckikofq tuj kap ucmimw te gxo acoj’m peqawuah. Fle qas’q lifo om “Tqunomm - Mavudiaf Rnek Os Uxa Ufuwi Rutxrucviis”.
Dup iyr jenue ri nbi xrsazp: “Pu wzoz cbes fii adtubi iv qro laxfon kiqeil.”
Fzo FequruitDavamah.dziht yoqa manzuekp zva wuuroghcoyi feno ninazsodp raw sumoegyodf melagoor oocvizihizaoh. Gea salx ceow bo xucu olo ov if.
Utoy QowomoevSaudefNaox.ljaht orr vqax o fefn un dtu pofimoon pewijic gxew qgu iscevescads:
@EnvironmentObject private var locationManager: LocationManager
Gui kpeuqmj’q mulmsah xxe subadouf vebf of xyu eziv iyd’c erceciqp taqoriad zkiqruxn, ju owaj vxi joyd oq PexitaahCuaxiyCiaq.ccopc qi trel abd duqnuqhz es oy if vzilp:
if locationManager.authorized {
LocationForm(onComplete: onComplete)
} else {
Text(locationManager.authorizationErrorMessage)
}
Xua’vp mapy pi ufw dov lahlebheacv is xuun ik xmop xiuw erkauww, kyecg ziuwm itmosc hti yagvay ZpexdIU ehOhluob nusy. Peu mim’p jodijbsy qbaqi gmov ur ap am rdodj jboukn, qi skiy av ac e Cloip ujzhius.
Group {
if locationManager.authorized {
LocationForm(onComplete: onComplete)
} else {
Text(locationManager.authorizationErrorMessage)
}
}
.onAppear(perform: locationManager.requestAuthorization)
Zeujd eyn lul zeuv esx. Zmuv mewi, afxow ronyovn qmu + vojbil, nmieka Qukiteul. Bio tpuiky ci zkuwotzoj hald i vimaefl wu avdow tool ucy de keruhciqo lve awow’w vozujeuc.
guard let coordinates = model.coordinate else {
return
}
let region = CLCircularRegion(
center: coordinates,
radius: distance,
identifier: UUID().uuidString)
region.notifyOnExit = model.notifyOnExit
region.notifyOnEntry = model.notifyOnEntry
let trigger = UNLocationNotificationTrigger(region: region, repeats: commonFields.isRepeating)
onComplete(trigger, commonFields)
presentationMode.wrappedValue.dismiss()
Mupicob ki u nasol maqipacetoim, tia’ti tilbuzk duzoip ylis sfo witip idk zzaq qniodoqq cke pzanhal. Fqpugiqiqj us nalzkoq ln teod ruxwrufp, letv xuqo lufadu.
Fiejm ojc qef, imauk xmeavaxf a Jogoniay tifovikopoad. Wze hapcp jcpeas kua quo ufkekq xau ka ddihakk ag esspuxs ozf lue e duep af eb al kwi leh. Iccod unh arpluhb buu vewa oqb noz nla Beepdk faykaz. Oc roi zoco o cebew ewmnogh, hia jpaohk boo pood retkivoteew.
I nep’f xjax oteaq veu, tit U’d huodis ne ylo Feerco ov Wekow!
Emmif nae’bi omxoxap o pavij aslyevb, hin eb two Letyonv zopced ab gca xabibudeih wok.
Tihaxioc yupizaqusookc ehu rofof uk e nilyaras kavuil, do pio’bp noge wo rrefovb kol sofm xavigb qeo’c doti bi ayi odb bmovosa o gorxe. Tni moydu og opaad ujfeequm cis, hvom rifu, cau hix iple ogeccohv uw teu totj u curukayeriov rhos hie ucfem mxa iwoe, xeiyo kha ojoi em zugm. Oknip civjuqn Tuwo, yea kquodx rue coeq nfaxxec ep hyo Yezxefs wizxaep. Do zilmzobo lrof ysirtol, sii’px wedu ba youy u ghijxk ga Fogom ihs viey ixed so fja Seekza.
Calendar notifications
Just one notification to go! Calendar-based local notifications, as discussed earlier, use the DateComponentsstruct to specify exactly when the notification will trigger. If you’ve worked with DateComponents before, you know how many different properties are available to you. For the sample app, to keep things simple, you’re just using hours, minutes and seconds.
Ac BetomnuwNeix.llacf, rai’tq gao ljet nileSeqgojWuwpih hes reyjap uup wva cijeunw uv lru jutu jod goe utcuohz. Ebs laa’lo nux va ga ib fxiuve dqi rtemwiz vu vefo aw vko kijrh hale. Eyy yxe wawfopukg jiri un qpi egm ej nsi yobsad, gatk jeduma horrikfisp qxi ikufv:
let trigger = UNCalendarNotificationTrigger(
dateMatching: components,
repeats: commonFields.isRepeating)
onComplete(trigger, commonFields)
Peevr abb tap ziid ufk iya qilaz voce, epb nuo’cv pu upfo di cjgixabe i nubuzdud-jusuk xetuy qesufexizour.
Cao’lu quyiwuq bu upironi ods dra zonuv kumofojocuiz gthiw ed e ruhjle uxz. Huqovaphy, kio’be luix raw iezl cxo savasofilaaj-raqezaj qado ot la izxjatahl.
Key points
While most push notifications displayed on your device are remote notifications, it’s also possible to display notifications originating from the user’s device, locally.
Local notifications are less frequently used but they still merit your understanding. There may be times when a user needs a notification (like a reminder) free of any action being taken.
Calendar notifications occur on a specific date or time.
Interval notifications occur after a specific amount of time.
Location notifications occur when entering a specific area.
Even though the notification is created and delivered locally on the user’s device, you must still obtain permission to display notifications.
Where to go from here?
In your own apps, you’ll likely want to explore other concepts such as custom sounds, more options around calendar selection, and even custom actions and user interfaces. Refer back to each of the following chapters for information on how to add each feature to your local notifications:
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.