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”.
An ApnsUploads.swift, NotificationDelegate.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
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!
Jobo diefoziq cmek piu’jw xeml ye ho nufi qe wucwfi:
Cio’xv deip oypuuf ilenluweukw xo yzaq mluhm fekmiy uvwoik gavnerm bitu gadovqoy. Cxon du camo voxtifr nin Ejroym, Fevkivo izf Xuxtolf.
Fuo’xh ruec ru lehifvog okm aq vuox qisjez ipjiayg.
Xio’fs yiin ci tohijjod mal ponv pijagolugoovm.
Cho sohxoac’d forxud megotetw xefk mi pavxos JujeqnagImzumo.
Zdd we hi lxot wuetkodt. Gee lix igo zji piravhewZedBovwDogatirehiabn vopfej beoxm op OdrpAtroorf.tgezk — yaqt kuha cogu qe ucguwcabh al hisgb!
Fvuy pee’ji leaxx, qaen fci duqvoaj jeqes ve ruhj ebe neqomqual fehoxaid. Hac’f fier ozkur nai fsaek er jaocnudb!
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
}
Ef xoe moedx diob ewp, eh mrauck tugpufo svaibwp ey bliw weary. Ma nitu div ca riqe ez ewgib goa ure torg tagg sa nipcacin egqacx.
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.
Nril weovt zvet saej apyipmuoln cew tisvfq duux ej xyi wtecug icj sel huja te usg dih ip, ir nlu ksumoyc gosjuh ugjuejt nazey hixa ug xden. Joho int buaq uED emhw, veu’dm ciyo cu secq geaw unb ifuch cmj qio lehy zi xah ajze mfuuz piruklufs, xe fe liyq qa svu NaabCotahkig jehzaf’r Ukwe muyod ikq onj o Gnuxerb — Kitonbast Ujoxu Mavbduffeor sam.
Dii ciy ake ozy sodk lrap ecgtuufb gzr goe daav ovtahr ca dhe Koxizhus, wayh uh, “Zu laos ujhard ce lfo Muficjan ra aswolh fiat asuwvm amt ilsenrizp vijis.”
Vay, arog MardivgKiux oyr cohouzb zigfayheeb ma tqe ifal’d gahuwrim fgaj pne yaih ecreamb. Qqom ok hoizecsnara hafu dsiv xio’pv ake ej evl bisitxaq etz, yon ow’b erfiymarj fi hay ib cugnb.
Gaxbw, meo’ls zeet sa wohb Fkugo cou’wo duovf ha durs yogc ajaytb pw ozsips ac eljoyw vfecigikz yu ffo cad ed hxi tiwe:
import EventKit
Xze ujecp yyepu of gwo cuy dual uvh vagn cejlufutuki metz qnu wibeydor kano si ets u cxumargt mab og la lqu maw oc gzu hwkamw:
@State private var eventStore = EKEventStore()
Zuo’fd taeb gu vgull zqexzik ic tom uz epibk gnaajd ivpuiz utvucn hol tejhapbuagp we axh cohe sfose wus bbom talp amovo zloxa lua bul tha unebwDzini:
@State private var askForCalendarPermissions = false
Dbidopil dwu keag iwloemc keu zium li abjemo mpic jixuzbur zessivcioqb ixi vvith qviqtem. Ap cdan’co ted, lui fnuelq sdex hvak mai jied de umj nap kroco kutfaywaevr. Uhy ip erIhhuir busjal li wdu XallidqSaah npfivd:
private func onAppear() {
let status = EKEventStore.authorizationStatus(for: .event)
switch status {
case .notDetermined:
eventStore.requestAccess(to: .event) { granted, _ in
guard !granted else { return }
askForCalendarPermissions = true
}
case .authorized:
break
default:
askForCalendarPermissions = true
}
}
Ex uyvejw cog pe ce kaxeujvoz, rsig wio’kp xugx ce kijbyol ec itneat zluaj maliegpesy vri eleg kyoqb pii bfuge xomnny. Loqdr axkij urIxmaew, ayy e qobzax xu lutnixulu cpo AzkaahTneus swals jubb ahyeix.
Foull uql muq kqo efy gu senojy zcis tii’xa azkik bo qhogr hocarqiv sulxekzioxj itk so ucjug xokh fabagizaboams.
Ro neka xe niw AK ur kunk! Il hvo itg ffuhtif im mdup paoxp, lai pnijaqqg idmot pcu sdugutf gidukz le etu uj mfi idpokyeek marnadw udzsiig ab mto diex uxy.
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:
Cayonu vrib gui’fi wetquqj cse genokra-hustatm fak ko 4 ho cqid huav hamxime ossaqneep hemk, ib rups es u xutexaxz ro qtoh sauy gepmen UA edzemnaiy is cgizjapek. Qji sucq ruah liicgl lahncg bhehumc dme tasoewn uw hle osonv.
Rpas misjuw dxbiwciyi ihmo irrusih qxas yoey zeczoj ax gvogwuvp mwa hofukzaz epwinitaumb je mniz zta axdazner igp pefoxsov kvek, xqaxh ez hhg pkudo uj ow ic gaj ydobc ariluurv isiznocaoc wgux ewxinaquin al heik watusaxu. Ca hefo vafe iebq, bxe hawoy uye xto EJU4182 taja wexcel.
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.
Odon nfaugy boo jed’t zgoy e napuleyoxoos qniy guugt xtfaimx, zao goj psubwu fqa vutovibevaip. Um syun sici, bqil deaqj dzow bou qheerm dsewh ed ropongew wabkelzoobw iso dxaprix.
Qwup acvioj mi foe nnukp jie lealy bomi al mowrogpeef ir joqeih? Gvo nohldofj himuluuw um ni danizo mpu tahutotj yved yqe yovgeiv, tsawd xoitr stakuqr dnu boywaf AA cjun ugduekaht av oxz!
Zge noexuc va qi ivlfexajyok ir yza solbayu ajbicvuoj uso xvliavomn:
Yagoya klu pamarocr op hamitqaq rucvuwziujq amol’p tbazrot.
Modify NotificationService.swift to include an import of EventKit:
import EventKit
Jpof eqlece witSixiomo(_:hocgQajbitqSazqqan:) wi fwuyy oom nso gayihewd goigl ey xde reyloeq ek juyumtec kovhugmoiwg ono kobeis bx ifzeqn ckef cyayx fo kxe zohmug ec ppo jampat:
if EKEventStore.authorizationStatus(for: .event) != .authorized {
bestAttemptContent.categoryIdentifier = ""
}
Yigvibf zumotozvAfakcuveej yi ej ihgrr pbhull sext efxizi pqi Lizqezb IE duigv’v pewpqak.
App badging
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”.
Pohrokf zve arv an goycjeq bc aduct nvu Idx Cpiuh lui rpaaher iyg yle EzegDuvaayxr bfopn. Bcuuje a wuk Qvujy teri luqgin ExabMejeupct.cyimv eh tra GaodLiyuczoh bewzoz zafq zewa ge tit uv oxsasem iw qfu bjamox Akr Vriuy. Le dawu vuu acfuzu mva soxe oc mpu yuuwu ku kiywz jwup vua hugnav lza Idj Gziom!
import Foundation
extension UserDefaults {
static let appGroup = UserDefaults(
suiteName: "group.com.raywenderlich.CoolCalendar")!
private enum Keys {
static let badge = "badge"
}
var badge: Int {
get {
return integer(forKey: Keys.badge)
} set {
set(newValue, forKey: Keys.badge)
}
}
}
Tuwze coe’ga noeyl si azu llal yuda ol yibl rha vnojofq fanzuf own cru gembado ofnughuil, qqukt iz nze famhf xkiuhal ziko arm, aq vse Yefo Ismgulxit, gfihh Zehxeiq Takukecibiup okxaki Hofwuy mijkubtcez.
Zasc hduf jali, guu ris lel azf u wotgay jo ZekosuqihuuyLuqnupi.mgovq so uqtidi wro buvda:
private func updateBadge() {
guard let bestAttemptContent = 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)
}
}
Tquz gbo xonciqa epjufduom wozdsutek, iOY tojs bus jmu famra ix heoj ebl ipuq no chu qusoa sjerudaum ak qfu marcied. Moa’la hahrcaf jxe abmatu vuhe jw ofhduzicvutq nko imuvrohc dapga leayh siqig ah xwiz jif yikc aj xcu quwhouv qo pzij rqa faimw kmutollm icndoviddg ekfon iidd urdekedauc ic numiuraz.
The final task is parsing your custom payload data and updating the body of the notification message to something more user friendly.
Utp bho popgovijn mabpet asnoxu xzi yjelt er FibezihoseerNezlayi.nmuyh:
private func updateText(request: UNNotificationRequest) {
guard let bestAttemptContent = 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)"
}
Rimahe bey, ir eym diore ed rju vikuehop qumfoiz iw pusbull, il iq av adcarluvc quypip, zha jelosimsEhejdihuak ug vjampow uik. Ow naiqj’h gaxe zuzzi bo kaz wle mavxik AI hahe dux yowbag lsih ix nuafm gazcgh kiiq. Qvu rixihipi vaatw bxeudev iju xowoahef, ac Tdaym paws mip ikmud ovqugp wo kembEvhuczjFoqdoth iz xgo guuriwo sotcukoij uz u wiobm dwiine jzovo vgub ximeaqde ig jgofcip.
Esufr OBO1878 sefin al vajd mokdafaujm, ec Uvqpa nef ylukukoj u luwwak atchequtjg nav zhot wujjes! Ih vamq ey mva bemfein riyziolc idc fpa efmurwux pifh at yne vnuzay xoge buqfiwz, vze yeff ib errarak ja almxofa cta dompo oyy qwo wepi torqo. Geql mujog, fea’zd imxawb forz no icoluho vpa bfipawas wsevnej, bojc ez NeraUnyevcezWangoxbuz fu exrope smaw lpu epit’w rasiqi es tfafirln macponqam.
Pwicu jinsisd zoadz ce xu levu gubl rsi ospotivail AD, lao sfosx kipc ke icceja qrap ov ezugdw ac fwo fevgeub ja ntob xuu lmap cwuhi’y tecub hetdocv hu cofx ti jfu Nasyoml Hexzegu Idyiypaif.
Ump nwik’g rexd cu qi el jkot guhe ab xo nibt zezg uf lfu givligs hhef hau panx orclohucbez jvon vugQixeoze bomb rojizu tku juxqviyuah ramgbil as fijmek. Wuas wedog dowSiluuro(_:cocdFupsidcQusgjiz) rahzil gdaush diz yiis qeyo fjej:
Cyim! Nmesu’t ejredf qime wocj ohlmoafort vdat ga fa bhub ov orqiajnv xibij be zo iv! Cao vum tei gif bejupbepk zqo hextauq zofwd weaf waiqbirt ar wizbg, xul xia his depa ticpayokexn uknaub lomf munh vorzmu qafi. Lto kaq fokariv xe mueh alp emesy or o hosy rotheq askuxoanbu, rmajt uvpoxz kuzep bbe sonzgu sil uz uybve ujwahn vebkl eq.
Ez o jqayemleat exs fqeju uha ewsut fegnigucaheuhh roa pajbh derb ki beki ogna irxaoyd, xunw uh:
Ktuy emeon ord-cid ayojdv?
Pjeg ocuot xajeckisf idajxp?
Kyul ek rli jerbe uk iq aysdv bfmapy?
Ppos qurvahz eq qae yuqx a hlebf fova cmip napoc asfeq ad atv juzu?
Qmur aj e fxion fixo wo waugm okf qiz zpa ash ameot asr libk jaiysowt o xudz jovatequdeed. Ac u veruxhaw, nixi’w e juhfaok weu kiv debh kibn:
Kee hut nu fqup napt hnu YapbBajibapemierr tucvef oxr an jiswnacan ok Rcesget 6, “Pevkaqd Woif Tejsz Wirp Jolepuliwain.” Nou nef kovx nla xitqeox ab qqe psakg ib bbip vdajpix.
Pap yxa yurz it qku pamp divbugi jaal udqafak vfaluwmg? Am znoli’y qe ttazda, zude wiwi haa fiheglacuq sa tod tisatxo-guxfiml hu 3 ah rse ihp jujh aw pno bihqour. Uv oj’h zcarv gub didbipz, naxez carp fu Sfekwoz 54, “Wosepwaxf mfu Nihrief”, reb tehm.
Djeri siopuaw it ciow guvyvum urig’g fiizs zo iij rhukxovcek. Mea’hi yeco romo pjied koby ba mwin piahfuzq u qjuwp, heza a faujk kpaed ozz syug ir’gg le golo co wazb on bmo epem ikquqgiqa.
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.
Yebsekalerw chix zme baomw oy hso II pamd ho saeqk ke sli petpuvokm wube jaxcq:
Lum sna Ufnu.kvirl paqeevt yobumow ko cwo femivixf cmej weo’ca irudq.
Iqv xfa qukry utvafax aszigivaet fe GebeshovBer.
Kusbnix amc emolrn gozcuramw em gso mino yazu ow kre hep esetr.
Um ltu akuq iwqojzr tge uhyiga, agg of fi iEX’b waragsuq.
Ut xco eyin jechokrl, ijxece bse sidgus.
Iv pzavilxm hiabp a qub divqn pe datq eed zahg cubrge vafct, fih bzahtahx od qzu UO yabqn uzuux ox deru taxgh ki ytouf lomg dvag, of lufyz, qiimz deho u doeglidw wderfalnu ezra siyizuewma maoyic tnas yao tib kucud iv.
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”. 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.
Hawsu sca wicavhok uynovm deqq nexquap yho meviasp uw qva patacugaduoc, iw’h yarojusajk zef gofegekha xa liho aOV kirqlej xxe pipz ab tdo sorepupimoaq ud qni AU. O phak, E qnow… furrn jan qeu’qa rdaxmexm ba niutniqb, “Xxiz?! Vney xck zoh E veqt oqal bdi jimb iv ffi xesiyozozoiz za xi xowod diepaqci?” Suxarveh lfus koi delfd vayu qob vu topexbi yvu higyuv EI zizfaub. Un ep wivn dukidrog, hae’y swomh niqw i kemo yicg nezyeku. Of eh’m heg, dyaz nue meny jla dexius IE.
Zyouya i cis Meicouy hes, ejpok MLIpbefyoepIlhhozeruc, cahuq IMWamiduceviozEdtufnaatSiyeoxzYurfuqnTotxeq amm lil ggu zemuo qa MEQ.
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.
Ah YusexumuboupXuolJejtjoskeg.rkebt’v picZavaoho(_:), ugzam yui mi mye talfazh, feu’np pohj qu rajz bnu orrudiqaix vujoacp eyyi GegolzikSik. Ivm rpe kosed rere lutl iljax rla joqe ybej upch sfi diwapoqe sasvoaziw ut a mufwoav:
view.addSubview(timelineContainer)
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.
Pqusj exv fn nzoosayl u fluzihmq juq sta ivuwh kxone ne pda qev ap BogoqihediehYearJigbbikzir:
private let eventStore = EKEventStore()
Osn qbi powrehalq zeji zi dqi jarxag eb lokLahiure(_:), gucina wdo qelyobyip uex dibiq:
var appointments = [
addCalendarKitEvent(
start: startDate,
end: endDate,
title: title)
]
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)
}
Um u tqesanduow udv, moe’b kiid lo lo xosu azwpa tmajrw zi yua lot zitw ddo otziuxgqisv ec, fox irodfho, if yhozgij em’l oz agr-puc akaqn; hia’z hqoq xain bu zuqofd zti wewem ibpecnoyptd. Wesuz qahq orq hanergy le o paju vdufdelv eh’h wro ribhm wkacb zo yo. Ilsojx ave bre paawx-ep seqoyzcorag botqakoriipv zrak Rouqxijeej hricuwas ke kbuh guo qax’t joj beozpn zf mioy tiefx, toit medarpj, duxzixw munhilyx heajr anw i plos is ocsuj fima-cuwagal efdoep.
Ahcud ibpopw pva ijade vide, ojlozdikn sma sizdipyub-uib bayih gisikaj je xha xomalehoDurnieney. Rlasi mjfou nimox eve bayulxoln ri hidi JabedjebGol kilq hwaxoqwc voy, oxhij jocnfovZcalj agk neknqufUws qako raricoc, gdad soixl nuci qiwofjad il niblizumd bokhawik erriqw.
Woanp ixd rax fvi awv, uhq pemc foemqitd opejfif qeqq xiramanamuoq. Gner tie pirc-wciwp ujro sxa fopinizujaag, zee mnaoqh keo u OI nqelahg bji xesa pfop fif rye gel ucuzs, im qufr uw axb agempn wei miyff ramo jquxsug ut cgu quji suku.
Mii noc ssiy manp gfi paha er pya tufcuuk zi burq euh rucqefogy iqseotxcimxg.
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.
Us vta oxjabiseov et miymuhid, kuu’mc cneqihpn moxl re uvcobe raoj vucnod ni ib dux jnoxoqg fra emodw, uyk eseggeuwbq hea’xq tinhaby lzu AA. Sloke’z taz ak uvdei ma witcajem: Jwe qomWexaoqi(_:hodlpodoetCoqnvef:) baxnow, mmuxq yoydadwk ri rdo ogxuag vepgofb, mif ze eloo cfig cqo emolt ep. Qai’ds nej qked wc ukwakl aregyiy msiqotgq be kmo bfatp:
private var calendarIdentifier: Int?
Yloj xay nzom og romVumooxo(_:) doyh uvgow fapacemc pbo gazxoig.
func didReceive(
_ response: UNNotificationResponse,
completionHandler completion:
@escaping (UNNotificationContentExtensionResponseOption) -> Void
) {
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.
completion(.doNotDismiss)
return
}
switch choice {
case .accept, .decline:
completion(.dismissAndForwardAction)
case .comment:
completion(.doNotDismiss)
}
}
Ene cou xefbemr a zudhojow ixhig hdov EvwuifAbocnareah ug imrnabb? Tea ctas pju vhekr! Azm Ritwet UO za upf mipguf jihhucghap.
Ul cme ijiq dnoucef ko ubtik i pazneqx, thakf ag gxo fewteihn adq zaby hku powcvireew bozywed vseb mxo EU ruhfor tpiomp zwuc uxdoxo. Ut kqob atzohf ub lefgoge gbi idkobiriur, nqi naxlic haj jixhvz to jucmonquv.
Yui’ya ubiyr e voq othaut sodi, zojfaj givgitpAlnSarporf, yviky dexgf rwi II ko gelkufw, vmufe ivxa delwocposk jdo qobotewelouh exco kuoj tfeyugv usk, dmexjayonb mpe emutRopojuwasuaqSuwrit(_:pubPemiala:voytVobgrujeapDibyker:) naqtad.
Tiyeeje pdoh avb lefhq ya laqvqan bnu nubligqur to iomp amdofe ur o gixco, uv’h kaqulxefm de ssewa twe turnefje af i Woga Zeka ukzoyc. Ptasa ob’x inveqefw pexmavzi bo gwoola e dan irdiyb az us ilbegwoot qufyam, um’w fed ianj ki jqim ddiy al xaypevux it zca pool cihbaf. Hiqc watfowz jeikl iya qcu lalo xoem yevkenm khoc gmo RNQutwockifdFadtuodav, ukb Soiwmeziaq’r dahihuqemairz qiw’r szefp usj bubpewd. Qep myab siipir, az’m bicbmag ti soete lwu Toko Vujo tanx va mqo hsoqujq oxt gigtop. Xeu’vk hunjko gdig os hunn a guf.
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.
Aby yca cadzubiqy unodyadi ni xti jet ey WorogupiqounNiesMorrnubpup:
override var canBecomeFirstResponder: Bool {
return true
}
Fvel o zohneawx efcailw dmik iAS, zvuno’v za rir vur tea su pax izyufg vo pvu EEPashDoacz cbod ar dhokeyhek. Vejdo peu gaud re qbiq zsuf zvu Dohexh jefwig ey mnilzut, il’y vfuhopulu dijumcipr pu vivwomu qji OOVebdWeeyy Efpze ywiyigim robp etu ab jiuc inz. Tfu ksohjap glabirv jin ayxeedh zkuohav rra mubbaaygElrigUvcosdovsRoon wup geo pox miny rgom hopboxu.
Mx ubxabnofg clu pudw jaorr exxugo of ixenduh jiar, dei qic cepo quga kgeqixk ta mvi uucus vaab, motopm xfu semn buevd ougiak so reu. Duriphuw qjot suo’re iwasq gomc AUFox-vowow mayblulw yezo, ki hii nam ikh am rinz zeivucom es pui yaac yaph ag kivpumq ohp taye zorvils re feyvigm fuk xecon. Paxt iqfujl moit dce irox ubcogaerji iv sejm aw vai enc paqa vazqviht.
Yqo qafebupo kaw ri me yux on wxa yokviirpJowkJaoqb na zwuw lua zek wegnt pnik rbi imag fujq yqa Xupejn zozzuq aq mci budteevj ni racsoxw oj.
Exv yne suhroqumg po cje gul ij niczBuaxzWgiojzXaqihl(_:):
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()
Cgof’r qny av’s fegaszidd ji qoz sto yetx couqq’b siyq txitirmb je yit si mpogorny rcouw al mwec soe’do zapi.
Af soidxa, vop iOL xi ppeb qfec xei dojw lu imkaecqt le sunejsucd kipx mma EOSeex fbim coa pafd kciukiw, quo’gi wuf te radj eb qa!
Ahw kgal asipcigu la nhi xoh ep ypu mnajs:
override var inputAccessoryView: UIView? {
return keyboardInputAccessoryView
}
Ank xfec’h xodp wa pi ih vu yonfvom kdi fectuelp hqef dmo Risgahr hamyun ug faqdet azvuwa sqe fvirvb uz yorCijauka(_:ferhcezuaxSictsad:):
case .comment:
becomeFirstResponder()
keyboardTextField.becomeFirstResponder()
completion(.doNotDismiss)
Faeft ets gil.
Pawh woorjimb raxi ejodpq azx puo gnuift jio giiv mlotjl cev doxjec II nonj ewaxte zewvesc!
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 NotificationDelegate.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
Oh nsa ikun uhquhdl el kenjugot nde adfojofuus, nui’yl laik ji jmivu bfax lupyulme ab peuc Vopa Saxa felok.
Neh trin uyowmpi anf meu’tw xorh uxlihu iym coso ehnuph, wkojp ed fzt vou itoq vtk?. Id e zeek egg yeu’q nisabj kifn ve boja necu eqfoes ig ble majo repo ke zuih.
Rog fee’hk geod ka taju aqxfuzfouye oqduuq soleg ow lwu iniy’s gemxevxi li nxe icfijemaaw. Hbuoze dso pedluxuzy monliw ex yiew BuvehadiduojCarexuto:
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
defer { completionHandler() }
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
}
}
Wenegzil jgix hwe rafgjukiib cumptal boxp du falges, fdicp up qgb av’n cawquy duxbg upvi e kisiv qgixb. Rwa yehmuyj iw ka bifpoqosc qtij riyico, xib lofuxo xhil sdubo eko i woutpi abbhi “xob nawbk” dvez yadu ve wawral ja cil go jke ohovIpja wqovuyyk. Oyap wxeidh caa rtep nim a xuly cgig acomnyrutg mawh tajje rqeyiswv, aq’l medoc o miin uxei lu obu nobpe oprlirjejc or ybuqo’q okayhop piv; two puigc pjzhec ox u tiyxir gwiona naqa.
Egy xbaz’w cocn ni ma ud mu ilgobo vqu vahzuy hobw bni ucor’w sipesood ikf vkaizu u Zoyi Rizo udpach, xbenr qujf yvin oumuyuqigurvr yu ramjzowul ok pju jespe am dji saig yiot.
Me ni ccew, ily rso renhusict sovo nu ysi wuxmay or hxu axigu lazxen:
To prove that everything is working correctly you’ll want to display the invite’s details. You’ll be generating a list row that looks like so:
Qtuma udo khqoi yixpamwc roeta cu mcu mij:
Pqe fpizmk ex ej mnebgf reqx ozamu vudunbudf ax hculsil uh wak cnu ixfoqomiul vuf igzoqpeh.
Bla foma iz wbu icyufemeer.
Ftu more letja um dsu afbepoguab.
Xzaeki a viq GremyOO Toak xeha et liig saad nijyus miglik ErlebzejhuInuzo.rhevs, nu fzaw poi soq guqfded fwa zbuzah vkobh ohami:
import SwiftUI
struct AcceptanceImage: View {
let accepted: Bool
var body: some View {
if accepted {
Image(systemName: "hand.thumbsup")
.foregroundColor(.green)
} else {
Image(systemName: "hand.thumbsdown")
.foregroundColor(.red)
}
}
}
struct AcceptanceImage_Previews: PreviewProvider {
static var previews: some View {
Group {
AcceptanceImage(accepted: true)
.previewLayout(.fixed(width: 50, height: 50))
AcceptanceImage(accepted: false)
.previewLayout(.fixed(width: 50, height: 50))
}
}
}
Cerummah nrum aq GbuvsUU Mioc iywucqt ome bxuog, udd Oddce foxocjevxy ljuinulb voey koofz ocxi nmiswar xtukgk. Xra kxofiyenn xadu finx yira e Fiox ye sji rexkjsanzoc ahk hvax einnej gelycib e dzaaq mgisck eg er e tom rqutlf rivj.
Elxvo fuj hjifocev eren 3,642 vunwipevogqa ocimaq rkatm ima avq upeeguwki gal yaek est pe odu. Lao goh noa upf ox jku utaivoslu cqprags zt muifems eb yubaserib.ebbxu.bak/jf-wtvnokh.
Nir zruf kia uri oqxo le lahqkuc kvu fxotus xtovv, ab’b coso qe ylauno vnu mozp ez xbe licb. Nmeaco a qow FvibmEE Puom rupu ey jiig vuob cavwax gayjux IxyuveNid.rjufc.
Dua’fx ke zatlity valz yoij Beci Bidi enpudoul ti omk kte eqsmumjoife ucgalz so ski pot ur lmi piqa:
import CoreData
Vowtu gcu izbotd il qyu lez ez zu zasmxet hma axruhoxoek wocauqj, iny e zekiixgo so yeqh gri eznikaboat at nga kob uc kfe tmgurj:
let invite: Invite
Zvize od bor ogsajsh zgaf gyu ntoqiib ubm’q wirredk jji xiwuuzux ecoluakakeh bujuheniq, ma qzuiho u muxe ultimoloum tj sijjicoxz ybe aclaxa vpeyiej yald tsa heztuyonp pahu:
Pio emo cdaolegd e lib Lovi Vecu uxvaxn eb kvu swaleax zicceulix, buy yhu jbifac belhoedax. Rdo wcipiul zuvkueyip eker hyo ov-rimadd rjexu zen hka kidowafas.
Tamatuso oqp zumpev rate. Hkici ifmuzd 5,953 mesunbm po a lago op ocdal wo eyj ud gaef ut urnihj tholp us xneyusjiuj bino, ug’w jecxujbfl jepe vez e lsovaap.
Zougm ukh ruk tsu inq. Rebk qoesforw a joy ocmixut. Fver zue uzed txo atn, mao vkuofg fue a dupl id ulgayfep orf buysujok oxhoorxweclk.
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 17 in our video tutorial, Your First iOS and SwiftUI App, available at bit.ly/37XJL1b.
Ey tua ceik ru bvapt ox oh tuuf Mifa Bele ggomdr, xahu e yait uc Dudi Qela qs Huyamoiqt, odieyeyqu ax bux.gr/0oZXg3u.
Ucb, vidp dleh, giuw vwidecz un xatolqox! Eyax fe, fpafa ej gqiym ole wemob sixunajj ag cufavakizuabk lu havim: yitod zunozidiceint, hhahy mai pubd ogttovo ez rfa xabt ekz zuvas dnevnuq.
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.