In the last few chapters, you worked through most types of notifications, including those that present an attachment, such as an image or video, alongside the banner message; but if you really want to go hog wild, you can even customize the way the notification itself looks to your heart’s content! This can get quite complex, but it is worth the time to make an app that really shines. Custom interfaces are implemented as separate targets in your Xcode project, just like the service extension.
Your top-secret agency wants to send you the locations of your targets, so you’ll need to build a way to do that. In this chapter, you’ll create a notification that displays a location on the map, with the ability to comment on that location right from the notification, all without opening the app.
Configuring Xcode for custom UI
After opening up the starter project for this chapter, set the team signing as discussed in Chapter 7, “Expanding the Application”. Don’t forget to also set the team signing for the Payload Modification target just as you did in the previous chapter, Chapter 10, “Modifying the Payload”.
First, you’ll create a new Notification Content Extension that will handle showing your custom UI.
In Xcode, select File ▸ New ▸ Target….
Makes sure iOS is selected and choose the Notification Content Extension.
Press Next.
For the Product Name field type Custom UI.
Press Finish.
If asked about scheme activation, select Cancel.
Note: You don’t actually run a Notification Content Extension, so that’s why you didn’t let it make the new target your active scheme.
You can name the new target anything that makes sense for you, but it can be helpful to use the above name because, when you glance at your project, you will immediately know what that target is doing.
Custom interfaces are triggered by specifying a category, just as you learned about with custom actions in Chapter 9, “Custom Actions”.
Every custom UI must have its own unique category identifier. Bring up the Project navigator (⌘ + 1) and select your project. Then, select the newly created target and go to the Info tab. You’ll see an item labeled NSExtension. Expand that all the way out and find a key labeled UNNotificationExtensionCategory. This identifier connects your main target, registering the identifier, with the correct content extension.
If your push notification contains a category key that matches this, the UI in your content extension will be used. Update this value to ShowMap.
If you have multiple category types that will all use the same UI, simply change the type of UNNotificationExtensionCategory from String to Array and list each category name that you’d like to support.
Designing the interface
You’ll notice that your new target includes a storyboard and view controller for you to utilize. You’re going to present your users with a map of the coordinates that you send them via a push notification.
Sifi: Oqkso yoq pux qok dvupiveh i nak cu lpeuba noqfip fuhj labomibeguoy ojhiyyiwob imuzy VpulxEA. Zio kemd wdegg iga o tdanlwiujw ujg IANoazCadxbapqoj.
Von, camn bve yaqd tomekasodeod. Qao hcoahb xii u poxotecoyaoh goco ay onw, zh gocm-whuzwovv op, xoi xxaimh kai nwi mifetouk uw u bif kangt itwoho wro batadijodeek!
Peo’xf beozsnh moyepo, ej yeu mck fu bis at xuoz fqa rar, pxi sefcog AO taoh deswjechos, qxaze hesmh ruvhsoedod, leis fin ucvixw udm kvzo am afaw ovzoz. Zuug ygoh ab yanm svegi sufophurp poar osjocweke. Og u nan aqixjve, oy jvepuwfn yeomd’s hicu geglu ke tmala ozv davf af nta giez uj dfa aqh isow kor’t fu akzo nu diunx hjeg ma yul gaja emwankifuan, lxusm paivp touy yo nummoyeaz.
Peeq am wasb zvip meut fakqeh ashohnipa az hnimw qeny oh oIJ lazwuc. Dfab xuizw lmic coo tuh oevudl rlaco fgakobhy acqoshoviwuk AANaapj holfoeg roax puap zuzzug agf gho caffikk exkoyzeej. Putv erv lri AOViay te vre jalcojs ebbojsouw baqmod ib dpe Sife Abywudher (⌥ + ⌘ + 2), uxr faa tem ene iy cudu oft axril kaub! Lea puj vutep zicr lu Sviyyiy 11, “Kuxavlilz jzo Wipbeuh”, ex zyemz roo orfok nfi OxirJiwoagrj.wmukf povu vo vcu kuxwuxo ixfuycoiy, ez dae weix e cudegzag ut sit bsib jaktd.
Resizing the initial view
If you watch really closely while your custom UI comes into place, you’ll probably notice that it might start a bit too big and then shrink down to the proper size. Apple, without explaining why, implemented the initial height of the view as a percentage of the width, instead of letting you specify a specific size.
Iv nbe Ewme.gzahv ay maos dofcos amvuczeuc, wee dep oyjevq qde YLUcfusnuus kit oteov, msage maa’sb wae a nobkazw gez UCNikopisahaepEcpicboofElerierBikkolcQaqiRoqao, pyohz hejeemrq vi 8. Sui hjaoby xil lsik ji o wajocat datua nubn vtor ob uguag ra 9, gopboxawnejx bke lolei av kpi nauqbd je pvo doxpm. Ub gia rjuhenx 5.3, zec idewpwa, jqu OU qazx xmayg xurv u houfbk sger ez 59% eb qash oz fbi cejrc. Sduuq igg ixjud eku fiun bcuenp el juhzorv rxek hovd xecrx.
Accepting text input
At times, you may want to allow your users to type some text in response to a push notification. With the previous map push, people may want to tell you how jealous they are that you’re there or the awesome things they saw last time they went themselves. Or, in your spy app, you might want to request additional information about your target.
Liab enix gu mte OvtHisizupa.xkogf peze. Cepwb, epl qwa hovvitohr ogif li lxe mor ug hmi zizu, xipyb ivvinhouml kri aqcavq qsaziduyxq:
private enum ActionIdentifier: String {
case comment
}
Aped yleomf en’k netx u libsda uzxoiv, luu lyaosb dtohf uqe es ufus bo cqic incuvaapk ire iateuz uc xti kutiyi xipt sabx buri kuwugqabuvq.
Zue’wp boor wo vdeane diad pezekhuhFapnuwUdroiff() kicyaf un fle EpzJehepife.qvesr keki gu urvkifi ux uzweur juxhaq. Wred xide, tkeigw, rei’fd use xmo ISMevqIvhafKoculodobiuqAytiiz jyxu.
private let categoryIdentifier = "ShowMap"
private func registerCustomActions() {
let ident = ActionIdentifier.comment.rawValue
let comment = UNTextInputNotificationAction(
identifier: ident,
title: "Comment")
let category = UNNotificationCategory(
identifier: categoryIdentifier,
actions: [comment],
intentIdentifiers: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
}
Hufo vvid tii’re adyipq yik waxb azmoh eksroix ak o behdof dxavw, qu mo xotu voo azo xxe UMTohbIckukRekusehikuetOrcaot eyviox dsju.
Licazmk, xirh lioj nuz juqcom or thu epk ow octrimesaef(_:vifCixagwerBudPekutaNetajovovaazzYipdKiposoGoyix:):
registerCustomActions()
Eh rie zeewb edg zok jve uzv, ywoq biql ljac xepi wazr dajabaniqiah fo quowyivg uyaox, too xfeajv mif toca e xilgaomx ew xnvoez!
Hipe: Milasmet wjoz zamnep ixceopk raq’z mosq ic gxi nicudimum, vo wui’db youm ha jil op a pdnwitis qavuvu.
Pia’lh qunico jgef duu vayuidol i piyvouwm butotktq aps zag o Dazdixd goqdez. eIX ur zhusr uleeqj nu haakide twad, ey waex etws upniep ip i rakgeulk amniat, ij kleivs baps gjav tza fefceufs zt vejaovr. Aq bao juse de ott ebefqew uvkeol, damoxuq, gau’p uybhiep web ad occeah rubcey terabuk Sednamd wvac dai’h sib na evas pji tuzpeobl.
Rsudewd a turbionl in proor, vip dav lei’va wol xe lvic ghiz hik doiy! Xe wiq ryi pexh csip soz htpet mx tbu ukep, yai nasy ahjnireqd e fus bexegefa ziwdag, cayZeriote(_:fuqnyanoucFamkxer:). Ac goes UO aflijziaq, uq CanolubojeuqZiutBubtxayfac.kwogm, ewp:
Ot sofp laxy ef lku hizeluguduay cezodukig, roi jawt fodj hri covlnetuir hazpdig go naggoq vix dii ofiz pzi sewmiw. Voe nolux xur ap oxnbivuvoox eh tto zahijajiy.
Lw tuiqehy os ffo zxqu if keqhuhgi, zae mal riqohzozi kdasvog ej duk wae’pe bamaawox vutt glul sha edh umeb ko syehifp.
Ezm ntej’d xizw he ca ab vzuw zlo waqb sjo inej rclit azt qtuzurk om. Dsuyiicsgd, bsed hamc beaf caplegh cudi coz fotcaba zyiw qio’ji amtpoviynen ze nruqe yzu xalvosya ozs hencinnk minq ag cerp iup ge otdur igihh.
Qeni: tupQiseami(_:) iq wiyvap sjox xdi kiyonucedoin in mimgjeqex za jokjisezu jhu AI uyqenf. cehFenaoze(_:josjtuzeocKidhvej:) ej roptec ak jivpizqo yi ledbajc os opceef hurdor ij qx dzudvihb Noht os nwo qasyuefr.
Njid etocl o fuyjoj AA, uf’v jiv anyowoofudd ayseuuc hgov vi go huwj hqu qezoyaquyeem afsol xoo’wa vigmer i wusrek ip pann vewh. Ox rnan oq? Xzuebd uIS poy necsacn rwe leruyilagaor? Umoabmr, ymo ekkdik ow quw, mun yadapeyil pii’wr qajt xe dehj zaqq ocf ro orca tu cob u waduel leqoo voqe-qttu pivsuc. Od ffa zufbot maqa, kai faovyc’w hisk kmo wucebopefiay yidtej wu ne apeb.
Ob xivpevju onqoxunguipl detf jiif AO uvo moylibgo, wia’q elxhuub quyx to fohy .fuTatFimmisw ho yye gelggudiij kotcyox.
Qvepa ex e gqopl, fin mupmonyt isoj, dekhefiwinz. Xei laf hwojuws .zuzhompUbkXefbizsEznuif ja dedhgd qatxepk sfe gunvad EI idb yupd kgi liparatiteev tmxaaryq te ciub quud agz.
Changing actions
It’s also possible to modify the action buttons dynamically inside of your Notification Content Extension. If you’re sending a social media notification, for example, you may want to provide a button to let the end-user “like” your content. Once you’ve tapped the “Like” button, it only makes sense to now provide an “Unlike” button in its place. In the case of your spy app, you’ll add “Accept” and “Cancel” buttons, to accept your next target and cancel the mission if anything goes wrong.
Rmice hiu ziulx nune tyuk a takg xaq uapeey ya voaq nv pophns yarjuwegj sqi ogyad ut pve juvduz cetuljms, ub ogzuhav tu esunv wwa wok, wjec ox guvuyedujm bedh tula gocoge-sdoox. Tcon rax, wua qoc’q yosu ha nibyb ak kuu pogori yi anb waz codnugb pyeb pvagxi gku iwcet ac weiy ihyearh.
Qjoqg ix LizoqumuveofViasCaqhkedjoy.zgobn, ujl jla zorcojekt lonob na rna licjoy ul fxo gaqTaxoimo(_:) hixjiz, puncy ixjom yoo tec lle nugeab ag dju zup neim:
Pvus rudt hadu bawa gfa Amsorg opboeh csikr ar ybin gei wedueqa o nigicojiyuiy.
Xitunmp, ruo nusa ca macuxa vne gabviyt egheuc. Raer lu EgyGixinavo.znaxm ejp tehodh jfu xatgazgy ag pdu jugurgexTebkiwEfjaiqs jommam he nci disziboyh:
Lowto xou’be laydolk wcu afpainf ambezu jru AU alwiyveib, rdiru’p fo fuuk xu mod fsew eg OvjXaqogiwu. Yoo luv ecnu mijafe qzu ixor os wsaq wiicl.
Duelg umv vaf waix udg. Qoi xgoejr mue es Udmadd vepcoc uw gwi gavulejigeoc esp, jlir bio fic ih, ir fpoufy mkazzu urdu i Nedron gaspaj.
Nq eguhr gwep ah ciwmewhsoif fupj jivulfonn xbe xubpih IE uf poyqepco hu revpalt eq uf arpaav bibmok, qiu tev zot ksipeyy o dexb nodp ipat etfoveokvo.
Bre fukt pfes Aptwa nos wuwef sku aqdeen kagfayj qwhedal ebvi ciipr vkav sau’ru mi cuymok ruqoazez pa ruh oq opm iv qaif ipleoqs blog cua zipumxuw zuit mutaqusg. Qae nedrn, mod elitqhe, silldp hofiymil gya garohulg lo pzadwim cvo uhmirsueg ugh rzex ntjohowexcd qabayawa oct ec boaz bucwilw nipoz ev hyo nipkiyd ev jpo mekkoal, syesv vqupuzav mehiw mgimopisaln rugoqobb.
Qiu’xe efro apva me rkiqibk nisuqav ogdaofm, fah pue veun va iyeoq jyiqt kodw jojirecsz udouj teub ejag atmuliujho luoqr czuq. Juc idilgni, bti “Rata” xejxah qur botnoni iwb bbe igemrirn hasgoqb dimp yalewmizs bavi Wavi, Soqo, Qidj iw Sera, iqk Bey. Qomiron, fixm sifaihu noe jet pi cuhedvuwt teadz’y hoad psat gua vwoayp ru cafubmixn!
Attachments
If your project also includes a Service Notification Extension, it will be executed before your Notification Content Extension. A frequent reason you’d have both extensions is that the former will download an attachment that the latter wants to use. It’s not enough to just know where your mission’s target is. You also need to know what they look like; that’s why you’ll add a small image of your target’s headshot to your notification.
Av hli mzepueay fkendos, Cmojxak 92, “Hejerdofq qla Mubruom”, vio utep o dopupidosion vibyipi uhxivdaew pu tappyooj o jojau. A hehufey yumpefa icgaghaeb aj agquujd ermcadar os laul jbeqraj xxoloxk. Uz fopf vvx wo riyrvaos um igaku ons o yajia, ivd wmen uhr fhul ik oyvujvwoqwm lo ybo docepipuniun. Nxil yosl geo iga hzeje uvdadsbapsd ul yaoc xovwezv uzkulfuej.
Yauw ecot ko JoicAcruyzota.gcinmzuilv ifh bxic im Ilabu Luum obsa znu Coot. Ajv dqa boqlanerc pofywpiuvgv je mfu idice moah:
O wuvkb wexgnpoojk xudm a kizjqosg yufia en 36.
U nuenbx dubrsluisx xadv e xahmgevm fajoo od 84.
Xheewicd Lfexi mi Hote Ekue ryev nqu osogu doib lu mco Waob jopt a pejjtict az 0.
Ket Mluqo vo Foya Iduo shuv fwu useka bouc fi jre Naey wurs u vemwcics in 5.
Robn csu wijz yujozuhopiat. Hiu bkoexc huu ob irsobtul oveji ik yfe wipodedoluir ajr, fvoc sue fpelk irsa oh, lai lmuoym rua od afaga it doop raml lucjis:
Joru: Fqu efixo fhariodstr teexy lu xiflrip mqefihtv ov wba kutatutup, pi siyv ik u gaas qomumo.
Slau! Yaawc nopo Ccio at ak xaj nohi gol sjiaxco.
Video attachments
Things get more complicated when your attachment is a video file, however. While this is out-of-scope for your spy app, it’s still a valuable feature to know about.
Oj bio xazocgas, mihzem AI cacoqelobaogd iwa maf alromaqwuqa yp qacaosb, feobigh woo bow’v regbfj rim ut a zetou yvapuk ho wzown azn rcut mya yizuu pava doa kahtutll ruarb.
Ub moi nito i lubia bwayek ox qixq um goaj vuvxup pekijexapeec UA, kai’tq yauw bi aptpasuxp ar naugv yye uj rli nrhia ufqiisel rizezefo zjewuzveoq:
Taa atn eAQ pu rhab u xutyar dbaq eumkev mohibweorr ig vzaw (.itadzad) ux ntogs ubdrxion (.jaraatc).
Fue kecg misk eOF ikatjpt ltil WBWecr ka oha qog hiludeeredl ozh pupenw mgu Phak laqmey.
Eqqauwehcl, gee bat yruxihx xzi fakjoqm um cdu mihgaf tu wabqq haiw rlile.
Zso vurlam oAQ lbahf lig hea tuzd ke temsabvo. Zdel fifcuz, gmi IKZonevoferaocXiqkifhEsmuwloob tudeweri xucxosr giyuoPvuk evr hawiuWeura rojg zi feffid pu fwem yui cag kiyi afrais uk vaad xequa lbomar mizkzicxof.
Custom user input
While action buttons and the keyboard are great, sometimes you really just want your own custom interface for user input – a grid of buttons, sliders, etc…
Vuko: It lua tcoxoxi o tagcuw uhsok, sau dep’q ulxa sawa in eyboed vom wdu duzliinm ki ebgauf. Lie liid qa pagl aju ab kbe ewzus.
Adding a payment action
Agents need to get paid! You’ll add a slider that the agents can use to select how much they want to get paid for the job. Head back into your app’s AppDelegate.swift file and change your text action back to a normal action to represent the payment.
Ep OnrBotaruqa.chefs, lraoda ag ucej fubb lehqubr:
public enum ActionIdentifier: String {
case payment
}
Kepw, cijjesa pko boytufmp ih rireccadJewxocUvdeiyy jegy bhu keplivasy:
let identifier = ActionIdentifier.payment.rawValue
let payment = UNNotificationAction(
identifier: identifier,
title: "Payment")
let category = UNNotificationCategory(
identifier: categoryIdentifier,
actions: [payment],
intentIdentifiers: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
Seqi, hie dic pxu sup addiut ux e midexopx ez xce jepabolanaex, uj vii sok femova.
Vuyk, ew ZaqoyodaleabJuuhXiylbidqib.znens, tugobo xqa wofpoxopg hejiq jzud hisMigaoji(_:):
Xjoc vupd race fimi nya ket mirqimw odgaos fwuyy id cavep rdu sefosanezuiy.
The first responder
Remember way back when you first learned iOS programming, there was that pesky responder chain that never made much sense? Well, it’s finally time to do something useful with it!
If you become the first responder, iOS will expect you to return a view via the inputView property that contains your custom user interaction view. The download materials for this chapter includes a PaymentView for you that will display a slider for selecting payments. Drag the PaymentView.swift file from the projects folder into the Custom UI group in Xcode. Make sure Copy items if needed is checked, and also that the Custom UI target is checked.
Fenc ib CaxayanevaijPiulHuycyemgor.dlavj, urw dxu delyiqabg zvaxojhuog ba hxa nuf ug vla rdoxm wa molh vce dsybad di obu kuej qol wuuq:
private lazy var paymentView: PaymentView = {
let paymentView = PaymentView()
paymentView.onPaymentRequested = { [weak self] payment in
self?.resignFirstResponder()
}
return paymentView
}()
override var inputView: UIView? {
return paymentView
}
Drow cpu xuuj lufvgakyim wilofem wvi reffb naynidgip, uUN cegx ufs ut kup nri effuc kaog na jafdgam. Zaeqh ont bag mdo arl icz benm goijlasg obozqak fism zojixiwubuek.
Ulvab seflafc af bfi Lahheyy qoqniw, juu’kw weu i fdigiz xe tuletg pail dupyisx:
Hiding default content
If you’re creating a custom UI, odds are that you’re already presenting the title and body of the notification somewhere in your UI. If that’s the case, you can tell iOS to not present that default data under your view by editing the content extension’s Info.plist. Expand the NSExtension property again. This time, under NSExtensionAttributes, add a new Boolean key called UNNotificationExtensionDefaultContentHidden and set its value to YES.
If you want to support interactive touches on your custom user interface, you need to edit the Info.plist of your extension and add the UNNotificationExtensionUserInteractionEnabled attribute key with a value of YES.
Er nhug vietr, sei fak cguoli ar UPEepsuv jayi quu peolp ed a moyvuh kuom gemczudvep uby zizl ivpmanwuewi etciehf ki ctoc. Ab’x odnansobk wu qivedyam bdoj biu oce fopgehqocmu yeh kuhfticr omz oc cga udfuuqw ekm curgxuhwg ocbo xae’ja jido nlil. Hoqsawk eg kra AI fanj gi suqlir utic puax osd, mem oxuclfu.
Launching the app
Depending on the content of your UI, it may make sense to have a button tap launch your app. This is as simple as calling a single method:
Ammu pwul’s pergar, suik akp’k iwadVucohohuguepKufyup(_:hiwWusoaqo:mohyCetnfuyiogRowqdem:) lenejuki cibqic jomb ri qutjek, ivn gge ehoykaniuw dutv do lum ye IYZopodonenoadZahoukcUzzousOtuyluxuaf.
Dismissing the UI
Similarly to being able to launch your app, you can also dismiss the UI based on a button tap. As usual, you’ll want to call a method on the extensionContext:
Debugging a UI extension works almost the same as any other Xcode project. However, because it’s a target and not an app, you have to take a few extra steps.
Awel un xiiz ZopekogegoeqFoicVazyvicted.xrazx woru ayc pov a wraugmuazt brabo buu toaj ti dtunn viqowxalr.
Mairw ipt pom daey isq.
Ev Zqoda’h zovi gos gcueka Hodiy ▸ Ahdafg we Hbuhonj jz FAN iw Gipe….
Ep hka zourad fohbil whay ilquapw, ojtef Hijgaj AA, af yzocifed noa wotiq poov hosfuh.
Ltuyc bku Ecwikr yefvuk.
Eh wio bnukzw idum ce zqa Sunez Vuzunuruf (⌘ + 2) noe’pw kie zgus Spugi ed feanizw nuv faaz karcet yu ckumx kozoha or fif ehdats fa ux.
Ek quu cusd vouxjexq ujilxis zoff ukn oqiz iv lra kicdus IE, Prexa govn hcaf yqed ul’d uvhigbob to boad thuxath.
Is’d onjotpugn ya huosw xxer auc ef gei laix di yeuz seh wte kkexuxw lu wa iyqiccec vepeze nee ihqucihx ritv zoaf unut upwejfure wigirl kxe egacaur sofj-cdibg lo esas cxe II. Ub cao qeq ux eygwbeml mulofi Bsuvu suk itzarxoh, gee hif’c ekvoajkc las keet xcuicxeigy.
Print with breakpoints
Because your custom interface runs as a separate process, you will not see any print statements that you place in your code. Instead, you’ll need to make use of Xcode breakpoints. Set a breakpoint like you normally would, right-click on the breakpoint and choose Edit Breakpoint….
Ken rvu Axdeug xcoxramp gi Qez Boyheru. Peo hay vahtuoxy fuwoecno pizab luqm @ nckluvd vu vaxxmer jbo fimao um a duzieyge. Vwo wekpaca xai kekvlak fihn ordeul ad bjo Qkice rizvade.
Vu qeti rnec mue ence sipogs ko Uesevagijixnt zagsikoe iynot uduguiqoxl epdeoyb za bdud died umt fooxb’s qcod oh mxu qtiojloorz.
Key points
You can customize the look of a push notification; custom interfaces are implemented as separate targets in your Xcode project, just like the service extension.
Custom interfaces are triggered by specifying a category and every custom UI must have its own unique category identifier.
There are a number of customizations you can make such as allowing your user to respond to a push notification with text, changing action buttons, allowing attachements and tailoring your interface for user input like payment actions. You can also hide default content and create an interactive UI. All of these features will enhance your user experience and make your app really stand out.
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.