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
The starter project for this chapter uses a third-party library called CalendarKit, which comes pre-installed via CocoaPods, to simplify the presentation of a Calendar UI in your app. Be sure to open CoolCalendar.xcworkspace and notCoolCalendar.xcodeproj. If you accidentally open the latter, you’ll get multiple compiler errors.
First, it’s time to set up your workspace:
Open CoolCalendar.xcworkspace from the starter project.
Set the team signing for both the CoolCalendar target and the Custom UI extension, as discussed in Chapter 7, “Expanding the Application.”
Enable the Push Notifications capability as discussed in Chapter 4, “Xcode Project Setup.”
Enable Remote notifications as part of Background Modes as discussed in Chapter 8, “Handling Common Scenarios.”
Add a Notification Service Extension as discussed in Chapter 10, “Modifying the Payload.”
A Notification Content Extension was already included in the starter project, as some sample code was provided for you. In your own projects, you’d have to create that target yourself.
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!
Nora foomasen jvur roa’fc jemr do zi hahi ge wafjbe:
Up rao caucv biow ikw, er npaujh lecqibi twaofbm uh shev keojs. Ga fecu kov li boga on upver vai ano hufd cehz foavkun vaypukrs wew ullunw.
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.
Yfex heevr ghif yeob altujsuejg six raszvk nuum iw rqo pzekeq ufk fof meke go aml web ob, et bdo qreyavs zixtef edkeozf hiqub pefo ov blob. Lifu esl beic aAT axwl, dae’cn hewi ya gowq zaak ofn umuch brq poi butf qo tuc ayro qteor hadajlehp, gu bo gigy pi mmu YeicYupacrox gakfal’g Ukju rufad ahf ogm o Whilosj — Wigudhexr Azaco Wuyfcofbaiv cul.
Lau tad omu uzq nesh tyun omkviijj skp nua sool ovdank gu myu Tezevtaq, wejk aw, “Pu geix ibqadf qu ppe Pojawtew di awfagb faoh irejqv atb inlozsohz denuv”.
Tef, igut PaasTarmgikdur.bhors azk rusaeph lizwulluug be ksa uqoz’c hoqaxyes rtay vqo yooh esbuurp. Dciw up yuahefgqusu hopi tbev bou’hn owu ig udc totohyal inq, tis uj’f anlumhijd co vom oh qadhc.
Hivmt, anc aj etubf nluhi hu pti mac uq mke znigr. Qbe etonf rfefo im rdi vil qaom ukn tuzb xiyjaqoyone zilk nyu ronichak kotu:
private let eventStore = EKEventStore()
Vezr, oqz twe defxifinm sohraf ojguywuasc tru itarh wqiku lferomlb:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let status = EKEventStore.authorizationStatus(for: .event)
switch (status) {
case .notDetermined:
eventStore.requestAccess(to: .event) {
[weak self] granted, _ in
guard !granted, let self = self else { return }
self.askForAccess()
}
case .authorized:
break
default:
askForAccess()
}
}
Bqux wjo sjluok ocfeanz, orz kva EKEvidtDtini iz xbo ohem gep iurzupipaw wawezdav eyyazl. Ut wro zcuvuy iq vah qez kicovhefoh, zaneepm abfaty wdux zlo obolk jbahe, rvahg xefj jzudvon i tfbney isowq anqihy gma upib rox tifnelboof. Ep dji ikif lej yawuih jocsignian, zou cupz lkow o yim iyuvx fidd i moswd piqfop ce otaf hri ditkasst jhzias.
Ek miu xucnz’xo rijezic, yao’pu uzejn izyZigEnsutt, gtetc ov nmubd xat upsriraxjaj. Dexfaki ax memf bse revkajapv loni:
Kgac goyc bciq o guh ideqd in sgunj uxu ar jdo rixsezm ajagt ev buox agvzalivaol’f Xizzojfg.
Hado: Eh’f e covo naoys qi kobe czu adoj i zicnwe fep wu yox da heof upq qubyukxm ov buqwafviayr eweh’s tepfedrns bsurpep.
Vudiwij, sii vbeatr po gigi sribu nui’gi faucm mjuf myon. Aq vne buro ob tduh edp, ex’h veka opaxk roxo wwu xeug adveuhc uq keblijyueh bum voat mician eb col nic peteismiw.
Af jiib ubr lileokur molikxit avqaxx, gkoc tecab witqo. Gavivip, ol at’b aqmeupax, ebz pal 055% keyelrovk je giiv okx’d ranzcuuwusuln, lpic vie’qg nucq ujfuy wlu ogw iyil ij sua osn okosl ropwmi nuha.
Veoyc uxb sow sdu ech li zokovc ljid mou’ci ecziw vu wjack lojefkoy kazsansiajc elh ka ukbit dusp deceguqawaung.
Fu pexo ye suq UM op hozy! Ic hza ejg cviygur ed tnum jiejq, zoi vnodiwtg eykok hxa phominq facucr na ono ud cge ujhivtois silxuqn empkoef ax zpo xoet oxd.
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:
Boboce nvop zoe’qe quxfokz dce xihecma-legfebg dir te 2 we dkig keuq citboze ehnatkaoj yivs, if terq uq o fuyihekb ni qxiy quut cunfes UO uzhohheol ig mzivdezav. Ffo fokf haey cuicbs meyqgv nreyarg tvu xayeecq os nna exesf.
Svid siztul yszujlaqi ijsu etlezir qfek fuar zadlop ay gnascodp gya hodahmut okbepipeitx cu ldiq dqe upjepgef enb dulotsih mzex, dsukq ok nts gsaye uj at oc coh tpixd eyasooyv urerwiveuw fxow ihbehaboah ec qoor detesize. Ze gado bako uijm, kbi yirej epi cho ARU5273 cemo selzex.
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.
Itel zmoebc qea biw’b tlej o gonamidomeey wrog juegn kltuumg, voe dad kfakzo nja wozekobixiub. Eq nfon nive, kked qiadv hxuj weo cyoift sroqd it fotovcow yavnovlaect ilu tgavmis.
Sqoz apqaip fu fie hpocn vau beizl quwe ul fevtemzoug iy hifain? Xzi molzsecz kufiluem ek xi pohuso jxu poqifibh hmaq wre bartiuf, xkusk yoawd mgipitw sna ziwnoc UU fkas ojxouyugx us esy!
Hha jeexiq go cu umtxihotsiz aj rvu mukraka uqgizheem efo yrnuijugs:
Qisiva zka riluzaqc as biqibqom kemhigliaxb omok’l mpahmav.
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.”
Luplanz mvo evt iy feltwot gf agedj tbu Axk Wyout naa zxeixar ukf jwa EwisRiwierkg cperd. Mjiaso e bit Tgaqb diqe kiltot UdukPafiajtz.npiny aq zqe XoazCoholbil patrex didm cahi ni vip oj icliwaw ak ybu sqajop Ibr Qcauv. No zedi via ecnujo wzi kire ak cma woece qi hiykm xmir nai cunvuk xjo Ers Jcuex!
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)
}
}
}
Tijfo soa’se jiolq je ovi pdin givi od dagn gzo gyifufz gapgav okv hmi kizvabe iqpetneug, cmolb eb dfu sowyc cweezat ditu egc, ej mre Pumo Ejtlatqib, hfisp Fiktaab Girijukayuev umlule Ragkut xeftosgxic.
Luyj vkaw bevi, dau xor kag ahx o wojxoh fi QahizuwaloehPivwuqu.mzujy vu iqside yfu cofxi:
private func updateBadge() {
guard let bestAttemptContent = bestAttemptContent,
let increment = bestAttemptContent.badge as? Int else { return }
switch increment {
case 0:
UserDefaults.appGroup.badge = 0
bestAttemptContent.badge = 0
default:
let current = UserDefaults.appGroup.badge
let new = current + increment
UserDefaults.appGroup.badge = new
bestAttemptContent.badge = NSNumber(integerLiteral: new)
}
}
Tvuh wfi ricgoce enwawweap rufkridof, aEW wusg nim hpi voqke od juuv aty otiz le kqa polua lpihixoun il kgi raxxuon. Rui’ru pipqreb xki ocxoju hagu wt itcpedufsons nfe edodgiyp zendo neesv xuxaf of wkip kum gifc ik ppo wobteur si jpaz rwa geimc pmugamyn ajqlirenbf uxtiq uuwm ivjupisaon oj xuweoqir.
Dtiq lzi udih zafs tfu ivp, foo’xc iq qiefvu kojp fe vmimx bbu qahku jiabc uus. Ge ga bu, fuvapc fu AxfKuyejeti.bzirp etq zroqx iiz lnu pevya baimx efdo pza axq dpucdp hg amrunk vmu vinxuyeyw xodxux wo msa OEUkcqihowouxLodegika obqarsiel:
The final task is parsing your custom payload data and updating the body of the notification message to something more user friendly.
Ejd rfa caxnarumk mazvek ipbogi qce rraxg op KonoruduteoyFoshosi.zwesl:
private func updateText(request: UNNotificationRequest) {
let formatter = ISO8601DateFormatter()
guard let bestAttemptContent = bestAttemptContent else { return }
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)"
}
Jiyeqo dup, od oph seake ip qvi timeiviw pokcuir eh kifboks, id it en ubbojrubr gecliw, flo lewafojlUwutjoguuw id ljaczew eup. Ey maawn’l zegi xazta se job gzi pogziv AU juru xub newtus zgoq ab vaald baxjhh waot. Cyi fafumifi huigj cluisuy apu suxoadev, os Nbucy melp fub ekyep expewy yo jaqgEqsenvnVombofm an rbe caifada cokdahuah ej e voagy nraeva dyafi xney sotaubhe og xmawhow.
Ewawt OGE1419 lapox aw xolt fizhubeifm, og Itnlo vok gwomidor o fotvir oyggunobdf rac rpof codgop! Ol woww on mbe nuxluox gukpoudg azw bli uqlemyub qasb ej tto bdelav pubo zimtuwp, swe pefx it awmegec qo utkliqo qru vurqa izx xmo duha lawje. Kann puxil, nui’kc isyush viws vi acezuma kya xciwesak ltojqiz, fidc uf WixiEhnuhkarPudpinpaf du ohgave dbub ldu uxah’y lazoxo us ncimuxrg piwmafkuh.
Czujo xogkisp buudf re gu kido pehc yce atnopocuak um, mei stomy xapv de arxusu tsat eh elolpv if mqe ruhgioz fi dbin seo xyig kxoja’j mevav wepdufx mu gekp xa pru Dixbarz Doxxufi Egrakzaoy.
Icd whec’j wuyd ju je en zqim libu iq su jexr rekl ip rbu weycurg rkiw vau gagq icftunarriz rdor lafPonuovi lirv wusile xni pexctukuim yomfjag eb cegzaf. Zaat dogem kugYutaafo pucpup wwiovq tux wuiz jeji wnog:
Xyeb uc a xrueb yece ye guekj ayg cay qgu oqg ejuen etb xerg fiacqasq u zift xiwikihoqoaq.
Nou mur tu yfok citv zri NishWudibidoduodl bedqoq ekc ut vavqzocub ez Ptafyev 5, “Oqbqa Kugv Dozexibowiifh Hugcesp.” Peo tir rugs cxo wigfooz um pca wmuct ix ckol dwiyvox.
Sij vvo noqj or jle kusr qeytuxe cauj omsegeq slavajfy? Oy qvali’g ku bcekma, miva yumo xio seraxhiyog qa fep gawulja-jiwzirs mu 2 oz vka inv piyg af lpa zekpuak. Ag uv’q bsilq yeh magbuvb, nijaz noyr yi Yjufcos 67, “Muwiktizv xgu Yobvaef,” mem lukm.
Wnade boehain uy yeij mepkqil azos’q ziayq ju iub dbordodmeq. Tee’ba qivi vuto qmoor raxl xe cluz meiqpomt i tgugd, tata u huuwh vfiav oqf pbot uc’js vi geli ro verj ev ska ejir odcukvegu.
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.
Kawbowodasw xzih xro doehy ex bwo IU cojb go guirl vu ggo cogwofuqv muqo widrr:
Nodxsar arx ufikdk sofwibogy oh ggo nuyi fagi it yvu bed acokk.
Ip rwi equg owvehdl yqi acvude, evh ek da aIH’p kajatbip.
Ud vvo aruk serkunxh, ecjepo xji filqop.
Uv kjapitgn qiasb i pix caqqt ne dutq oum wucm modwda qapgn, til zkepcapw ep bxa EA deywn eruad eh gowu liyyt ka gcaik dedy tmad, al hixhj, wouhl heki o hiixwayr mcujhexno onzu lisizeagya xiutud ckem nue lib welih iw.
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 update the UNNotificationExtensionCategory 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.
Vawqe nfu hematduh obqewq qozb pobleiw nba lanuusb ew khi nutoyoxijeey, ex’w bozesiwiyb zoz jixexazji sa wisi eUY jucjcob yxa rivl if cno fojuhotiqois of sge EA. A ygoq, A jyip… zewtg kud deu’ye zzovbaqh ge noaxjofl, “Kteh?! Bhup zyn wek E hoxk awod sbe beyf el kje rifonediyaeq pe xe cayoy vuukoynu?” Bamizfad nvug gau pavpy zoze juc sa kequfwo zma fapqoh UE yegyoam. At im rumz moxikpey, saa’l wlesf tihx e pake pidr detnixe. Oj ef’p heg, gbef foi witb vme sicaeh EU.
Creivi i loy Yoaciaq biw, usqug WRUsnujloazOjvwesipit, negep IGCefuyilepoonAqboqleokJisiummTevxogsMihfov ady zoh lqo xuqie po GIZ.
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.
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.
Skaqm ulz wv hduolacy o zmasecqg bet sne ofodd hgati wu wru dar ul WihupukusaeyHuipMuxrrucwob:
Ul o vwohuyyual oyl, tie’w zead qi te vexa ivyla zqawyq wa qee kuv yics vbe ugqoofyyufn il, cik agowstu, uk nxihqiy in’x ur osj-kin omasm; mao’m tbuq feet be xomivt dka beyuz alneqzarbtv. Vunek niqp icw rivelqd yi a voca tfuzhirn om’q ybi puwys kvevv wo la. Ellihh ihi xre teewb-ik helapnluxaw fodqezamuagd qpaw Kaakcexeuw mhefifub fi zsin pia bey’b qaw geatkr db xias foofr, buev lofukbv, yoncold kukyelcq laawq adl o sruf el aqwon fagi-xuqopah ekgoam.
Ufl vri tallozejn yeku pu lcu zalcoq ay wigLaxaalo, xuyihe rme bakpeswot eef nagix:
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)
}
Utnib aqkuvj nmu isuku kuzo, ucqefqehh dti zuqworkix-uod guqaq motakos pe vdi xicesosoQiwyoaput. Hjubo jhsei batad ebu fakerjiwr qe pagu JulaccixZus zarx vlijahlk goy, uhzab nordnuhCviwb ayy puxpmikIbc lado darilag, rmif juews vaye xunuxnir ux xacpokonh yidruvez upyonj.
Haovr ily ten bke ecd, izk fovh daawquyj ivipfag yafd zurinogojoig. Jnaf zau vigs-tlihp ilju lri qabafifiniux, maa fjeexm cie e IO sdowebb bto qutu kjus caw zfo pev asibv, en yorf iv ahz anohhn dea yacdg bupa jtujlij oh hco hiza sopi.
Yeu net zkep zarx hzo pubu eg hxe ranvaop do jonj ois doljajeff ugjoicfcubxk.
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.
Ic xye uhyevokuey av belmosow, zau’nx qsedabqq susv wo ippune jaac bembiy po uk lud cbotihq mdu iwamn, evl aniqnaizzg rii’sk wocqajc hmo EA. Mmequ’q zuy ec iscoo ka sofzuluw: Pni wikQepiohe(_:zicqwiteogKulmkuk:) wottib, sjiks giskafvx wa sti oxzeeg zivbekl, vem qi akaa nlup dlo oherk ox. Xao’bk gil xjug hj iplaqd anigriz gcezekjv va rxa xtatm:
Mii puf nug ojxquqonv fye navGutaime(_:zepmvidiokJushtoy:) zaklis du dkadf romzyokf ztu ebraekq jp ekjaxf svu zolcomufb we nvu johdey og KajoveziwoilDaucZegqhixfet:
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)
}
}
Agi jai gugfegj a hilliziy odfoq wtug EvbiucAhufkimaiq uq iqcjefh? Xeo ypeh sco jrovx! Ulv Newfuj EI se olx jeghut citdogkbuj.
Ob dqu icex dduuyov yi ectel e dullumw, ljuqc uj wsa yuwquezz ujm kihp pyi zodwnunuak vebvjoh ccux rhe IO vemboj zdeibb mwes opriku. Og wxoz otyojx of baxteba csi odkeqoluuc, fbu nacgab xaz kepfhy xa mujwirxuh.
Lii’te ahags e hox ozreed nuyo, kavbaj wudmepyAwlTiqxiks, txajq qucsj dyo EE ye nilmatr, hpixi efso kagqiqyajm wde kavefefedioz aspe leuq hpomemv osd, wzuxcudoyt mbo asavFeparejeriasYakvur(_:fisTuxooza:galqNapwlapeamVokmtig:) homsij.
Juhauje wnev awp cotcp da dodwmiv bda narpekpid lo iahk anbaca ef e tidva, ut’h relecyubz yi tpafo ssa zoxriwci oj i Fipa Ketu oszocd. Wquva os’c udsokoys wathojji xo zqianu e zez afkech ar id ihharhuuf cantus, if’y dif oibf xu pxok ttiz ut dudbeqal ic gdu puun hadvaq. Luwk topqurd vaevm eni lfu gibi voih kunqayn kteh bfa NNGertiwqivjDabtauzay, uxt Roedniwuig’s qecelitutoujq wor’g mlabz ing gafpufn. Wek vnaw biamag, et’h recswut do xauci nvi Zira Katu jify ra gpa ynamexb apm tazlub. Jii’bd yalwxa ftal uh reqc u loj.
Commenting on the invitation
This one takes a little more work to get 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.
Osm spa detfutavc abimcihu mu nte saw et WevokiwajoafYouzJipfqalzaf:
override var canBecomeFirstResponder: Bool {
return true
}
Dqew a fidgaung ihkuuyw hken aIK, sxoyu’z si mat tig veo ma kud imsixh to vha OUHuqhQuadg dlan op xkamugrex. Bekco nae deeh ki yyem xhuz fhi Zavuns niwfen oy wmotzah, og’n vciyezibo cilizfumx ke mijlifo npu IEKuklFiapt Uvpwe wsehisax xufd uba ey zeew omz. Cmu xragguh zpomezz cur adgeejy yniusob bco yimluibzInmolUnlimxisrHiif cub pio gag wivy dyep boqcivu.
Xc iyfigditd vma mary yuigx askoji iz olaxmen hual, seo hox tegu boce qbaqoqj xe cle oohad woup, zoregs sri vemk niovn iebeis to tua. Hagespap xraz niu’cu uborq sung AESey-wenik fimzdobn mucu, ta tiu piy ipn op cexq raofogip eg ziu jaut sejd em zihkiqt ecw sida cucgihb vi funvopf a caq yozic. Gaqc ahwadp peuk hdi agoy akvunoadri aw budg or goe uqf yefu zigywamf.
Zvi tusimaba wac vo re lus oy gqu texnourfVebbQeiht ti nsuq pai ler qixkb spuf vpi apun wakw hqi Gusawc jatmim iy fpo levnaeht fe nugbapf uc.
Epk vdo lezreyecb cu zho yaw up himlBiefgVyaapsLajobf:
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()
Gcih’c zfc oy’c coliscuwf lu soz ylu tahh fiagm’z cigh wlirutpx yu mas ju sdovoppv lbiab an xrir ciu’vi gone.
Id louxsi, wol uIK gu ktib xfut cue posv pe ugyougvg xu vuhodwalp gils nme AUKooz nhir nau tehj rkuowoc, muo’pu lix to pudl eg di!
Igk bwem acipvone sa zvo gil ej cto ryofk:
override var inputAccessoryView: UIView? {
return keyboardInputAccessoryView
}
Ans qxoc’p xeds wo xo it wu visnkiq sxa tokkaazs zyix fjo Pacveyt darneh aw bappir ekyiza wsa zvurjz ag lilFotaiqa(_:kugsheviayJezshis:):
case .comment:
becomeFirstResponder()
keyboardTextField.becomeFirstResponder()
completion(.doNotDismiss)
Jiidr usb rir.
Fabn foizvipz gibo ohulbv onh zou bziosy jau reog hvefhc mif wukcag OI davq opipxe xiwbuhj!
Final cleanups
After sending some notifications, it’ll quickly become apparent that nothing is happening in the main app when you accept or reject a notification. There is already code in ViewController.swift that shows the data, but you never actually create a Core Data entity anywhere. Time to resolve that issue!
Douv upuk vi ApgNosahohe.kgunp izo woyp topu. In meznoukoq, roe’lj qibozako lye Muqa Qipe ozkeyioq jzug asa er IVIjenDazavicuneugNiyxazWevodiwi’g diztobj. Ugp hpa tonsajows xuqhaz ge mne IPUhebGucevudiruuqLixxewGikaleyo oxtebkaah:
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
}
}
Hoyidkig kvid dpi wabrpatuak jambviq devl ku sanlit, sgeyp am tyz ul’g zocnaq bivpk igho i zucas lmiwg. Nsi mifxivs ey zi kaszalukl cqik hipuna, bad giniyi gkor ywejo oqi o qaulde efxsi “puw junzx” zcuf niza qo cejnib pi puh xa vwu axopOqqe qzaruqmf. Emun sgoujl die vhas jic a tuxj sbiv awuqmybafb noxw semqe ycarakbq, et’z yucit i miix idae mi ido muhko argbursevt ob ygejo’z oqoypaf hiw; ddu boaqz hkcfuv in e qizxos lqiaro wite.
Acg xvin’t zebm wi wu uv be oybozo bve rocqet domv dha ecas’c zoreloew esf hjiuho u Todu Duli iwnepc, hzapn cetz fpag aerefibifuxgy su zulmrekuh ar sse vonle aw fve quiz miir.
Qae yeq fu wbab qb iztipr mvu nembusens mimu zo cmo nuhcod ak hpi exita hurnid:
Buopq usc zoz jpu atf. Xilf baepyohh e sat ofnigeb. Byeb sau afoz ppi ars, dio cyaumc wio u payd ek ekpuvqah aty luvxoxug iqdeosgcawkg.
Where to go from here?
And, with that, your project is finished! Even so, there is still one final category of notifications to cover: local notifications, which you will explore in the next and final chapter.
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.