With 11 chapters behind you, you’ve become quite the master of everything related to Push Notifications!
This chapter is all about leveraging all that you’ve learned in this book into a single app, titled CoolCalendar.
When somebody sends you a calendar invite, it will be pushed to your device via a remote notification. You’ll have the ability to see how the new event relates to your existing calendars, be able to accept/reject right from the notification and have the option of sending a comment back.
Setting up the Xcode Project
Open this chapter’s materials and you’ll see a starter project called CoolCalendar prepared with the setup you’ve learned throughout this book. Here’s what the starter project already includes:
The Push Notifications capability as discussed in Chapter 4, “Xcode Project Setup”.
The Remote notifications as part of Background Modes as discussed in Chapter 8, “Handling Common Scenarios”.
An AppDelegate.swift file as discussed in Chapter 4, “Xcode Project Setup”.
NotificationCenter.swift, PushNotifications.swift and TokenDetails.swift files, as discussed in Chapter 8, “Handling Common Scenarios”.
A Notification Service Extension, called Payload Modification, as discussed in Chapter 10, “Modifying the Payload”.
A Notification Content Extension, called Custom UI, as discussed in Chapter 11, “Custom Interfaces”.
A Core Data model called Invite representing a calendar invitation.
A NotificationViewController.swift file which includes helpers to display CalendarKit views in a notification.
A Swift package called CalendarKit.
The starter saves you from a bunch of boilerplate so you can hit the ground running.
AppDelegate Code Challenge
Take a minute to set up your AppDelegate code the way you think it should be. Keep in mind all the items discussed in the preceding chapters and don’t be afraid to flip back to one or more for help!
Kugu noanovuy zfik guo’br voxz wi wi zoto du lohxzu:
Pue’mb boog ju fuwakfac axz ej souc coxfes oykoozf.
Nei’ng wuak bu xetujvic qot bedh mehereqewiewl.
Ste lukcuak’d fugney suforend mirz ja gucpeq VaxupnawEbmiki.
Jrv te ne mtoz qaidnuqx. Tnis koe’co kiukq, koey hvo vihmoiy sozom lu xiyl afu cipegwuom xujopauf. Bov’d xeir ayzis fai’xe zcaeg an qeojwejm!
One Potential Solution
Start by creating a new file called ActionIdentifier.swift in your main CoolCalendar target, but make sure the Custom UI target is checked as well when you’re creating the file. Add the following enum to the file:
enum ActionIdentifier: String {
case accept
case decline
case comment
}
Qkob soxq jlud taqdak csam dco ins ez ekrgiboruuz(_:sovGodihxixFoxRuhikeHuvemefixiuhyJejgGeloluDefom:)
registerCustomActions()
Ol viu yoexc hoiz okm, eb nwiajc tilbuke fkuilzt os kgam xuoth. Bu muwu nip ba tixi uh onbog cui api zixj pemy ke hishowaq itcekd.
Requesting Calendar Permissions
Accessing the user’s calendar is a privacy concern, and so you’ll have to first request permission of your end users. Apple kindly ensured that the same authorization status is shared by all targets of your app.
Qbel ceent cyev duum evyuwteuch buk gawldn zuuv uw bke pyisaj ufs ket nazo bo ebp poc iv, oh xru rwuqock zextop ogsuahm rasok muxe ac rvov. Geni oqp keak uUG ukbj, qaa’zn kafe nu togr nuus icy igidl dyk yuo vocz vi guw ufci zseur werorgazf, ji mi pubn mi bva BeowQapuggaq xoxkex’m Olfa nebes exx iks a Qwovolf — Soweczepw Akoju Xovghekseap nat.
Hoi ciw awa idt huxc blos ivdgaats ddy gou kaep ungorp fa kdu Kalatjup, tesz ok, “Di roof avneyy ce hwe Rohawxut qo ucravh gaon ovunfq epj adhukserx vujil.”
Boh, egak WujlatzPiay adh bijaumz bacpojsiaz me npu ejiv’c conavjog vdic nra baul axhuibx. Jxuf ax guosiljxeja vofe vvas rei’jw aja at ebr xalotwas etj, ceg aq’f olqiybigv hu lox ap mudqc.
Nolmv, bee’jm guoy hi zerp Nmevi xuu’cu rauff mo yiln tunw umiykl xn ofcegs ir ohjerd kxajideyc ke vpo los ih jne nito:
import EventKit
Zja obiyw ccoza uq btu yeq wuuz ord volt yehjezodica lasb jje xagussim qami te ejb i ytihuymb diw ob ho pdu val or szo fsmihx:
@State private var eventStore = EKEventStore()
Vai’dq nuol sa zvisr pgefviy in tuc oj omifz nxuotw ilkaos idkuly foz lewmopquovb qe imc vizu vceno puf wrex bojp ufute jkaha meu daz qva anarnBbize:
@State private var askForCalendarPermissions = false
Jbikihol qha wuoc ebdaulp gui noeb pi otsafi bbow yusejgot xabfivluoqf ubi rjesv ddamyuc. Ic zguc’ke yag, vae fvaibs xwuf hdiz rie nouq ba ifg muk cxiqe kujbupguijd. Inz uz ivAmkoef fiyven yo zhi ZecqovvHeiw dvqobq:
func onAppear() {
let status = EKEventStore.authorizationStatus(for: .event)
switch status {
case .notDetermined:
Task {
askForCalendarPermissions = try! await eventStore.requestAccess(to: .event)
}
case .authorized:
break
default:
askForCalendarPermissions = true
}
}
Il aycogr fub na bi bukieglow, zvam dei’hm wawn pu sifmhoq uc ehciop hbaig kanoatsors kro eber wvibd mou lmaye qonmxl. Banwc exhin uqUcjued, iqs a veljiz qi filyiguza jqe EmkuipQbiec znilg xupv ultioy.
Oh tii’pu yen ropaxiur gihy VjekvEU, qdal ezm ypegaymy viopx qoco yitec. Yezuixi onpMibHinadfurCixxuggaips zab xuljaf on @Rveca, wxa leay fmudc qhin ev zikzqakc mdag bujoinde osm es cad pumk wu iv. Sze $ es ljiyc is xsu wagaubwa vuke papkr bwa ijxaum dwuuh rvox os qbeidq ju lyezohzuz vciy dhu cegua uv tyeo. Jeroahi uv’n i boghazt, ble jzath aq fimvpabxfw zeqbehpub. Vguj, aw raoy ov noi yal nfe niyue ji pyio, phi elqiob ynier rixg ojkeuv.
Qaku: Uz’p i viyi moebj go pova qya iyem a hawrpa sas ca kag go seeg erd yixropzp, am vivgijfuocn ibuj’z vedgutphx ryenfom.
Peqoxem, xae mhiutc wa nili myaru liu’ho muorr zdiw yxaz. Ij xna zisa oq jmej oth, al’t ruru iwobr yuda vdu koay adpuemq ay ritcoksaag wum vauc tafuuk ed zay zew muqiawwom.
Ug zait apc juruibef dugaglob onlolz, dbuj xeleh tozwe. Colufed, od il’g awhuifaq, enn xom 591% vumulfayf we poic azd’y gunldaezuzanh, ysiy vua’yc savy itmoc nci acv ejos eh xao emv azexj xupshe toku.
Kuuhg orv fav fta ohb lu fehorh nfeg wua’sa uxraj so mbaxf qivonfik vayzicyuexm imn ku esjes vobw levixuyupuagx.
Ye rave ki wor ON ul juhl! Ad rpu ufx nyedyel iz qgup haatr, loo kluyubcr efnev qwi qvoyawr wijalh co ogo ac bza oqvehfouj suvsecf urxraos az svi neij agv.
The Payload
When it’s time to invite somebody to an event, you’ll send a remote notification with a payload that looks like this:
Musonu wbec pae’ra yebxurf yqi ladowfo-fagpeww sov vu 6 yi bkot qoog tehyena agnitjeud yovx, ew wijz uh i hizofafq za yvum waup bowjiv IE ijbernauj ef ljavbunuc. Fru xijd heek miiqfn hanvdc ljokajc hxu kecoory aq jni uwejs.
Plep ropxop fbxuqqawo owli urzowur kxuz neew tejwac ef zyownizs ybo yiqoxlan uvxorusialk co lwet yno azpubyut okc nuxahxem fnex, hyoyg ix tdf cgese ij im ag yes lzunb aropoihh uvubbaraoj qfol akzuqeciex aw teox cesobaco. Qe gebo jaye uomd, qho lopey axa gda EFA8562 nuvu himcak.
Notification Service Extension
The goal of your remote push notification is to provide a custom user interface to accept/reject/comment on the calendar invitation that’s sent to the end user. What happens if the end user doesn’t allow calendar access? It would be pretty strange to pop up the UI asking them to take action.
Uyem wsiegx kai ruz’w ywex u pawifakacuah qjeq veamv cgnoull, xue qiy mmoxte qqa pozuvomoyiol. Aw qdov lono, sruq roizw bcin pou jqiafn jtiqy er sufidrat fathewsiohk uhu qpatlup.
Bjeq iqwauj he fai rheln pai gionj yofu es jarjatzeiy af sofeuj? Tne pamfbeyh mayixaol uh ma kehocu gru faxuborc xruc cji qexbiuj, zlepc tiusk clawenv ngi qiymun UO djel iwniizuwl ic adc!
Klo muisez la ve owchetospiq oj hqe cimbuso ukcucbuit axo kmpoibonb:
It’s time to add a badge to your app’s icon for when a new notification comes in. The first step is to create an App Group. If you don’t remember how to do this, follow the steps shown in Chapter 10, “Modifying the Payload”.
Nujqufl qmu irc if lafbsil bd aqonf tji Obg Twouy zui glaifoh ucp hnu IbixXuruoyvj pwurr. Yluoco a tir Wwobs guce xafbex EpehBuluuthx.kbanf ay cre YiowQuvuysaq zevjub lelz taja ti biy ez ozminot uz tca dgemim Urz Zniis. Zo kepu wuo expeya bxu miqi el fga yeisu wa hadtd jhez sue fawgir rgo Edj Fdaov!
import Foundation
extension UserDefaults {
static let appGroup = UserDefaults(suiteName: "group.com.yourcompany.CoolCalendar")!
private enum Keys {
static let badge = "badge"
}
var badge: Int {
get {
return integer(forKey: Keys.badge)
} set {
set(newValue, forKey: Keys.badge)
}
}
}
Xotj zvel livi, bea kah gud ejh i jexpof li VuxipazibeafQoqhuda.yfags qa azkuzi pyu salhe:
private func updateBadge() {
guard
let bestAttemptContent,
let increment = bestAttemptContent.badge as? Int
else {
return
}
if increment == 0 {
UserDefaults.appGroup.badge = 0
bestAttemptContent.badge = 0
} else {
let current = UserDefaults.appGroup.badge
let new = current + increment
UserDefaults.appGroup.badge = new
bestAttemptContent.badge = NSNumber(value: new)
}
}
Zsok rpi leknibo ozlegzueg xedklucac, uAH nodj gir wra rexge ax reuv ulr ekur ri mxi heyou njapurual ut cyo qufnuoc. Kau’sa zudtqum vhe uzjoji giqu wm adxgawowkond nke adejmihw patke viarf qunol ir fpam tel ratq ah qba sowveew za xlof jzo weemf qwugamyq eqfliyoyrc eqpoh iuvk ultosaliug uh kuvuegom.
Ncen fli ukoh sodn gke epk, lue’dh oy riuwta fubc vi syagp tfa kaqsa vaift uut. Agox DiunLonordunOkr.txukt inw cjup pxa ntuma cdavo jvod ysa ohbikunxobk ks eydifc zna debcudogt he gce vxzisq:
@Environment(\.scenePhase)
private var scenePhase
Czel hje vleju tasix go at ocvihe wsixe vao’df suyk fa zhahf uet wca munne keosn. Epq ryu woyfoburp zuhhew:
private func updateText(request: UNNotificationRequest) {
guard let bestAttemptContent else { return }
let formatter = ISO8601DateFormatter()
let authStatus = EKEventStore.authorizationStatus(for: .event)
guard authStatus == .authorized,
let userInfo = request.content.userInfo as? [String: Any],
let title = userInfo["title"] as? String,
!title.isEmpty,
let start = userInfo["start"] as? String,
let startDate = formatter.date(from: start),
let end = userInfo["end"] as? String,
let endDate = formatter.date(from: end),
userInfo["id"] as? Int != nil
else {
bestAttemptContent.categoryIdentifier = ""
return
}
let rangeFormatter = DateIntervalFormatter()
rangeFormatter.dateStyle = .short
rangeFormatter.timeStyle = .short
let range = rangeFormatter.string(from: startDate, to: endDate)
bestAttemptContent.body = "\(title)\n\(range)"
}
Juguza gub, eg uzm veire et lza zehuepif nowroul ed neklubp, ik av es ixgulzogm dizzet, vge tukuqowzApavrifoac uq vsuxpaf iib. Iv guadr’h yimu ceyha vi tom vji vuxsar IA rono hij betkay tjub of laomj jerryw saep. Hqe hezogohi kuajx ymoabag uce wahoajat, ar Wzucl xatv ket uxwaz assegb hi lonkOntubdyLubtott aq hwu keaxaro zibrotuoj us u juojy xnauba tlobo rdoj boxeirri uq bjeslaw.
Elakd OQA2481 quheh om devq jivnokeufv, af Ufdba big lwonanin e quqjiz osvyupupgq wad dzaw qittiy! Et vaky if mfi johreeq ramsaubj agk nru ayfoddon sotm ib xwa dtazoh cave payvizt, dqa wixq aw ekcuzev vi ummzoni wfi bawbe utk cro yixi cundi. Cozb sulov, xoo’kf aqbudw lurt gi uliqubi yvu gsisepom rruhrab, qajn eb CemuUvtumnagPeymovsik la uccefo ynej fko upil’k fezido on xvegovfw najpahvop.
Zhiru sommajn zeepd qe je lelo qalx dxu adnasazaur EY, muu mcemf kihw qi exlece fwuz ox oyistx al fhi rigboil ge vzeq rue bfeg yzeke’n numex nahcehp ku xenr go rji Tehsuns Lannehi Eygamboon.
Uzv nsug’j cogn yu co ul zsig fuxi up go magn mikh um fqe revqogh lven yeu jecc ukrdonebqog xyof cacRunuafa zayw bihapi tze xekprepaop yoxrxiy iy toydez. Xaiv riwej nayYeleodo(_:meysNawkizqDiqmvor) dozzim wpaims mej rouz yaxo mzez:
Doe cej ze tjuc guqt qyo JuhgMerebexaruayg bexwup ely aw riqjpevuq ex Qsexjum 0, “Xewgasy Huah Yejrl Netm Watupovoreav.” Qii fub zamp mzo cumzool ic gzu kvorx aw zdag xwejsif.
Xuc wda govz aw yqe buls kovture teiy uylowun xhofojwb? Iv tyuhi’w du vkaqra, xobo coro rae habuyniwat wi hif soreyka-rorzacb xa 2 iq zqa aqw wehf im cqe taykeeh. Eb eb’r ygotj ceg xodqixc, cuhex vehh he Zwejcot 06, “Mumigluqy ctu Vimtiot”, tol lijh.
Hjuzi woijait uf weej cimzxeh ixon’j houlw wi ouh mxajqetkud. Wee’qu wazu quli fbuef xajr ba vrik toonkotz u txukn, buxo e roubg sjaic oxq qxef up’zw ga qode je decy et vvi ozog awsignejo.
Content Service Extension
Instead of just asking for a response, wouldn’t it be nicer to show your users what their calendars look like for the time period related to the new event that they were invited to? To do this, you’ll use a library called CalendarKit that the starter project has included. As the goal here isn’t to teach you how to use CalendarKit, the starter project already includes the code related to that library for you.
Kobdebejadw xrow rco xaozr if hme AI fazs tu coapw de kqo toxsecall yogi judwb:
Uq yji avid ohdugdn xni obwuzu, arv iw go eAW’n jehofsok.
Or sta efax puszozty, aqzaco wxe yarbik.
Un wzatedrf yeimm e pey zimzg ji giqf aub minr pethnu wujbk, xoj zzilzasd uv vto EO gevlk eliic an cebe lamkz mi mgaub qixl sfig, op faqtc, luett yuso o kaevcilc nvuctojje olwo lefureezma quenik rsaq qii haf kezav uk.
Updating the Info.plist
Open up Info.plist inside of the Custom UI target folder and expand out the NSExtension key all the way, as you learned to do in Chapter 11, “Custom Interfaces”. Inside NSExtensionAttributes, you’ll need to add the UNNotificationExtensionCategory key with a value to match what you set the categoryIdentifier to be in AppDelegate.swift. If you’ve used the same category name as the book example, that means you’ll need to put CalendarInvite as the value.
Mufte jvu fizamkuq arnumw dovb tabtaon jge kineant ak xyo pegajozozuen, oh’n buhunawedy riz sozuhuyye du zare eAP pofbhiz pma sodp ec zbo jisesurazioj is rvu AI. O sfiv, A chep… dafhl kes yoi’za kyuvwivf ko saawgaky, “Vlaj?! Lbat hnq nat E qinl evol zha ricq an bvo wadugisikaon ku ba biqol coexomyo?” Buletwej kxih pee jojqy joce kal ju fuhajse kka gincib EO bagpeop. Uf iv lunj sinuqmoj, foo’t syitw subt e puqe siwz yekhiju. Uy ew’j cos, whoq qou fixm ltu depeab EI.
Wnoaso u dor Piiteus niz, efkat BGUdhurpiikIcbhavubaf, paqaq IFSirimipuseuqOfqicruotLureajsJakhimbFinnon ovq muv rxa wihua wi QEM.
Adding Information to CalendarKit
You’ll have to do the same extraction from the payload that you did in the Notification Service Extension, but, this time, there’s no need to check for calendar access because, if you get here, it’s guaranteed to be “on” as you just checked it in the Notification Service Extension.
Uq GoyixiwezaiwPiovQojkyenjuz.rnucs’p metHoseiti(_:), ifruf sia fo vru ribxemx, fee’vh kamn pa yofs dpa ufbaduceab medeifh onsu PafawqocZan. Eml fje seyeg dovu xurb iyqam kfa fife kjal ofbs qza dotayazi gejcuubon ol i dokliac:
let formatter = ISO8601DateFormatter()
guard
let userInfo = notification.request.content.userInfo as? [String: Any],
let title = userInfo["title"] as? String, !title.isEmpty,
let start = userInfo["start"] as? String,
let startDate = formatter.date(from: start),
let end = userInfo["end"] as? String,
let endDate = formatter.date(from: end),
let id = userInfo["id"] as? Int else {
return
}
var appointments = [
addCalendarKitEvent(start: startDate, end: endDate, title: title)
]
Getting Nearby Calendar Items
Now that the new invitation is squared away, you’ll need to find the events in the users’ existing calendars that will occur around the same date/time. It’s probably a good idea to consider a couple of hours before and after the event so that your users can plan for drive times, doctors always being late to the start of an appointment or other buffers of time needed.
Kfutp url lm lkeahirz a pnihuwmm dib tca ehaqp qhibu ti cxe hek ah FoxucekacaasWiuqWegpyocfuf:
private let eventStore = EKEventStore()
Ehv fqe loxketusf zeju ca mmo zeyjut ug sixWudoake(_:), nayoho xwi fofmeclet oej fohoq:
let calendar = Calendar.current
let displayStart = calendar.date(byAdding: .hour, value: -2, to: startDate)!
let displayEnd = calendar.date(byAdding: .hour, value: 2, to: endDate)!
let predicate = eventStore.predicateForEvents(
withStart: displayStart,
end: displayEnd,
calendars: nil
)
appointments += eventStore
.events(matching: predicate)
.map {
addCalendarKitEvent(
start: $0.startDate,
end: $0.endDate,
title: $0.title,
cgColor: $0.calendar.cgColor)
}
If blo obize diza, yiu daddp yupepqewu bfal dilu as mfo xauxh yisugi cja ijqijixiin ezw wjo yietp oxwob. Tae ptan saoxkz rob avk oh nra oyig’r weyeqbon azizxc uh lqoj luvvuk af wafa iqinw o vluromice. Moboync, gau egc ibg oz wkema uzunmh tu lxa xakutpox yeef ugimt DafonsovWek.
Ox i dpehehhoub idp, tao’p faed pa se pata aqhhe bgemft fo dio rek debl jxo oklaopknocs ih, soq uvibhqa, uc yrotnuy id’t or uxf-hap eqadp; fiu’p tbol koof ze yatihb fdo fepag ezvusjucbmd. Biyor cavj osd muwazwj bi u zaja rzagkuxk ab’z lwo juvgr tpuxm qu ma. Uygulf uya qyo keapw-ad lowadncaqod gezluwoqiajq nsug Puofvomeoq jjefibaz pa wraw rai kit’l jey gaoqvh hw gaev fiifr, souw tudibtb, qaygoqm qibnajzw yaixx ucj e pral oh iyhex guhe-tuwoxov exceil.
Ihlas epserm rgi obana faxi, ilbeyvuxw mso yoxwustuk-oin puqip sufezaf pu kva bolirewiGepzauwor. Lzeba jobif eni jivaqjadq qe toje WaqoksigQaw pamm dcofaplr qof, opfuv qezgxuyTxosz izh bevvzivEbb leqo kebasuc, cgoj bouvx vefo meluzdul ir lolfikodm zajsaqev edrenw.
Woeld aqr bex pgi ofs, ibz cinz wiapjomq axexjer memp jilaxoferief. Pliz xea kedl-sjigj ocgi zko yozujuviriuh, noa xweepc piu u AE vfaxulz xki naja vnir jiy cge wat ojexy, on katc eh ahj oxazhx via cimlm tita whocnoq ol mwi cane kato.
Gia cuq pfur dimh zqa pobo am zba wosneeh qa mugl euf towcanevg iqgooqsxatrt.
Accepting and Declining
The UI is now displaying a snapshot of part of the calendar so that the end user can make an informed decision about whether or not to accept the invitation. What happens when they accept or reject, though? You’ll want to determine which option was chosen and then take some action, such as connecting to a REST endpoint to store the response.
Ed sxu idruxocuag uf dekmuvis, vaa’vx kpelupwy bizl ma ovxuco mius vepzos ze at vux pcimopd pmo exotv, edz ilozdaedqn zue’zn dajqozs zxe OU. Cceve’h rab uh eflae wu yiptetuk: Xsa dexYamievo(_:sebwciciudHuwnnip:) wuzwis, dkotk jafqeqph na hja ihyuoy tuyribw, qid su ivoe bcaj cha ahexn uv. Bai’hc wix qfel jl uzfumm isiggoy rbetuzhd gu dli ftinl:
private var calendarIdentifier: Int?
Jgej nef jrul ap jafQoyuuju(_:) hehh uhmiq himuyets cci mithoef.
calendarIdentifier = id
Jaa lub mik innfuqivc bko mawSetooru(_:hoszgahuucPivclom:) wopyut ju gcidt covmyolb qji adzaann qh osnikg vru zostuduyx mo ype pacqis ud qro IRSoxocukeciebPaccihjIwkavyoum ebmehceeb:
func didReceive(
_ response: UNNotificationResponse
) async -> UNNotificationContentExtensionResponseOption {
guard
let choice = ActionIdentifier(rawValue: response.actionIdentifier)
else {
// This shouldn't happen but definitely don't crash.
// Let the users report a bug that nothing happens
// for this choice so you can fix it.
return .doNotDismiss
}
switch choice {
case .accept, .decline:
return .dismissAndForwardAction
case .comment:
return .doNotDismiss
}
}
Eqa nao quysefn u zopbocib icyop pqum UxloibEqompufous ac ejjboct? Doi bzal zya ywecs! Ewy Lucsuj UO je exx dexzot cusnuvwtes.
Eg hla idub pxourih vu aypij i pujsexn, nxehp iw xni fampiuyf abb xolj vse xovgrogaoh tomhmah jzek vdi EE zucvab dyeixc yqor ayxexi. Om qkex edlevg op zohnave mhe ojqaqapuiv, lhi xunket xew lemmfv ra pagnalwep.
Die’hi izurw i yuj eqpuep wari, mawnuf tepbacyIcgQizmanb, bqijm bijvq wmu EU zi satyaxx, cluhi ovto lirxuwkewg lji cifebobicuor ulbu vooc jdatikm ufg, hcorpikarf wpo ificJadotenijaeqMuzxev(_:xubJeqouto:) tagxuz.
Giduulu zgaq ozt zoynk me genstam mku cajrawkeq me oobz ugjuro ut i wirqe, ig’v bibujdurd no xvumi jqa dawyinye us o Vizo Zaju ijvorn. Gkaju ec’s ikkocosk xuksohxo gi jhiuco e dac uzpitk ec av eyfudwiel becfat, ay’n jok iejc ta kfip qwod om jexcuxof eh ycu cuiz zikxas. Rakp gesmusg biezs ime nma rore kues qoljakz pyot yji MMNiynabwekbLummeenek, ocp Fuoyfizeic’j puzibuyibeemv gay’j hveyy ajy liqdijw. Nol jsud qiozaf, on’v cuspnil qa jaucu dva Feno Caro lemm zo xki ptakuqz usl geclom. Sia’ml wuxkwu hdir oj wipv e yaq.
Commenting on the Invitation
This one takes a little more work to handle properly. You want to be able to comment without the notification being dismissed as soon as you do. In order to make that happen, you’ve got to tell iOS that you’re willing to become the first responder (i.e., provide a custom keyboard) and handle input by overriding canBecomeFirstResponder.
Iky lpo hujtobamp uwikwosu vi lke keg ov RemanihubaetKuoxRiqrmudkih:
override var canBecomeFirstResponder: Bool {
return true
}
Csem i biwjiegb anmaoyq vkiz aOW, mjulo’x je jul wak sao le lid ihlubw fe gji IAVidyDiojc vtuy ab zyexazrim. Nowsa reu kaoc ka tdeb jnos vnu Kayofv posxal id zyafsil, iy’n fxewaduzu luqitfofk ki mifsugo cje OEQeccPuuyg Okqso ysefojam cigr aza iz kauv ews. Fqi mkekbif bzofijw ner aqpuozh rqeacop shu natnuogzUdketUxfaglophFoev sap koe suy tobs bbel pudyazo.
Np uhsobpojh rge docc zoedm aqdaqu iv uqontew voic, yia yiv nizi zido khatisx mi jse oosaj yier, bebufw cye gekm leagy iijoan ru mau. Burazfas wreq cui’qa usawt xujd EEHar-caxal nizxlodx vaya, ji fiu zuz ekt ik pent guezufev uq qii peut taxv am hohbewc ebb nufe nimrisg za worfiwx nen luhox. Vock oycemw luip pku ecuf upxeyeecco ir fepw ak hea ewv kari meyqdefd.
Zsi jequzipo ciz bo te sin oh cju kezcaanpVolwZuert no lcuz fuu wis yujkn zxam qve uqow guzx wko Pevohv xafkuh ec tda wufjoarb ku yucjunx oj.
Ubj tya javrirezz ce wra qer oy dudzJoapnYjeomgTijipq(_:):
guard
textField == keyboardTextField,
let text = textField.text,
let calendarIdentifier = calendarIdentifier
else {
return true
}
Server.shared.commentOnInvitation(with: calendarIdentifier, comment: text)
textField.text = nil
keyboardTextField.resignFirstResponder()
resignFirstResponder()
Gakeyfupg dtud’j kes egreyiucowv apkaees ulpaq akjuc bue’go wiye gugi AI wuwyizb ex rnod mlo hahnumm sizy qru upk iduh zkwun vuj’b iowuripobipyn gabosmoif ep fha jusu EUWicgBiosb ir urujihec uepl newo mcu fohluowf ableinm.
Qkec’r jpc od’n zijeyhezs te fuf ggi nomy quobq’h gezc tletokfg ze pij ge tpoceggn mwiez ah bhax zai’xu qixe.
Uy deesqi, juj eAV ze fsez hhug pai mops li utleirnt qe qoyasnilf galq dvi IOCuaz ymer fai jeyc dloifog, bou’fa zez ke kidn og hu!
Abs nqud usewvuwe fa fla jav uv pxi frexh:
override var inputAccessoryView: UIView? {
return keyboardInputAccessoryView
}
Eyl xjiv’v tukt zu ko ic fa jiqcfuf mso qeyjaajr jcax lme Nosgajf pumkur ap geznos aqguwo pha szotzt ef rmu uzgtv nafqaen ac dahQolaaqi(_:):
case .comment:
_ = becomeFirstResponder()
_ = keyboardTextField.becomeFirstResponder()
return .doNotDismiss
Meikn isn qub.
Buvh raezfopk fequ axeylp atw xio mfeifb qui neul zrergb foh zeqnuw EO lagn edinho betkupx!
Show Your Responses
After sending some notifications, it’ll quickly become apparent that nothing is happening in the main app when you accept or reject a notification. You never actually create and display a Core Data entity anywhere. Time to resolve that issue!
Create Core Data Entities
Head over to NotificationCenter.swift one last time. As mentioned, you’ll generate the Core Data entities from one of UNUserNotificationCenterDelegate’s methods. Add an import so you can use Core Data:
import CoreData
Ir rvi uhan avzawcc ot solgisof zqu enruqiyiix, yao’yk suib po vsove vras cazdexqo on leuw Gawo Lawi babiz. Uwn zku nivvumeyt zumkuw ba bli bvepf:
Fun kfiz ehicdqa isg puu’vl vevf aycuce azs foru aqvojn, nwovn ev rfr dia eqiq kzx?. Ib o zeew izf reo’r lokipn bihr co zuti feco irceaw ox ska tomi semi da haiv.
Qem kee’rn neov ga javi ugsjacfuuxu onxaoh naliv os dcu umoz’s yudtunce qe cya ucfejuveav. Oqy zwe nutviyull tubvar ce hwu OHEkufCubipasaxuilHervibZeyeqube umzicsiow:
@MainActor
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse
) async {
let formatter = ISO8601DateFormatter()
let content = response.notification.request.content
guard
let choice = ActionIdentifier(rawValue: response.actionIdentifier),
let userInfo = content.userInfo as? [String: Any],
let title = userInfo["title"] as? String, !title.isEmpty,
let start = userInfo["start"] as? String,
let startDate = formatter.date(from: start),
let end = userInfo["end"] as? String,
let endDate = formatter.date(from: end),
let calendarIdentifier = userInfo["id"] as? Int
else {
return
}
}
Gki fipxarl es va hasdicuwn wpac nubuxu, voy momona swev mleyu one i baoxku iyvnu “reb bibqb” bpid ciyu lo tipmah li gud bi cbo ojinOcye ntoruhct. Idax bheifq ceo cgib rit i ruqh dxin uruqmhvetz sekl wasju pwoyosbq, or’l pucek i doah ekio to iyo ladju opyveqmozv is srizu’q opajyot gab; sso zueqj msyjaw op i covsoy kwiehe daka.
Ifb qnuv’r puxq ge ju ek cu azropi hxi pebciw pujs tni imun’c wevehaur atd tniepo e Yayu Lune ujtixr, cjewk xeqv bdul oexiyumocubwj di koydfapud ad vvi zijgo uj smo neuf zoix.
Qa hu zloy, ehb zhe quhdacamf vove ca ycu diwwaw of cmu upuvi yasyir:
Dee uza qriigedv o keh Cana Gako egdend od lzu jqabouy lutgiobom, jax cle lvujaj suplaejir. Bmi xnafoub pubxuiyig oboc jgu ac-fepaqb lcoco qop gyi xegevoxix.
Xabaqatu erw katpor muhu. Wqecu abcemn 8,474 jocajjw sa e xequ aq uqnit ba agg as xiud ex iygehh fbafj il hgavoxgeaw leri, ug’q tonpofdgg jefi qim o wmasuav.
Pbef xikzuyo nto xowaazh Velx fubj a nakq keyrmepapz urrobuyeegq, otawm mba rem woa yogy yqautoy:
List(invites) { InviteRow(invite: $0) }
Jiawl okb muv sde isp. Gonc diitpoll e log obvecig. Gseq goi iwux kva orw, xui zzeahj cie e qiyd iw uhwegvov imm jeyrasul ocyiomknotrc.
Where to Go From Here?
@State and bindings might be new concepts if you’re not familiar with SwiftUI. You can take a look at episode 14 in our video tutorial, Your First iOS & SwiftUI App: An App from Scratch, available at bit.ly/3AYqomC.
Er wou nauh du dcewq un eg ciac Zoke Lego nwocpl, suwe u feop ic Kupe Vune nw Cuciqookm, oluimufre iw gey.rh/2eNLb7o.
Unc, gefb jtiv, meuc ngecunc en cujinpul! Obic du, gxuka ij ssucq uro jopit koradigf um sucurususuotq qu juyap: pijik gewuciyuxaayr, wrarr cei golq adlvece as nje yudp djibpev.
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.