In the previous chapter, you added multi-window support to your app using scenes.
In this chapter, you’ll learn about context menus, adding support for long-press menus on iPad and how these menus port to the Mac automatically.
By the end of this chapter, you’ll learn:
What contextual menus are and how they enhance your app’s experience.
How to add basic support for context interactions.
How to implement basic menu items.
How to make menu items dynamic.
How to implement hierarchical menus.
Ready to experience the exciting world of contextual menus? Great! It’s time to get started.
Introducing Context Menus
You might want to jump right in and start coding, but before you get started, you’ll need some context around the topic at hand (pun certainly intended). Before iOS 13, implementing long-press popovers and content previews was a messy affair, requiring you to hop across several different UIKit APIs.
Luckily, there’s a new kid in town for iOS 13: A unified content preview and context menu interaction called UIContextMenuInteraction.
By using this new mechanism and its associated helpers on UIView, you can easily add context menus that change their behavior according to the platform your app is running on. On iPad, you trigger context menus with a long-press gesture. On Mac, UIContextMenuInteraction brings up menus with a familiar gesture – right-clicking on an element.
Look at this feature in action in the Shortcuts app for iPad. This particular context menu incorporates both a content preview and a context menu.
Now that you’ve whetted your appetite for context menus, it’s time to jump right in and create your first interaction.
Adding a Context Interaction
The most sensible place to enable the Journalyst app’s context menus is in the sidebar. Why? Well, most actions you’d expect to perform via long-press or right-click will be taken on journal entries. Over the course of this chapter, you’ll add a context menu to the sidebar cell and progressively create a full set of handy journal entry actions.
Avoy lqu kkizbay fqimadh us mmen dmevjuq ojv wu bu FaohSiqguGaowDujtxukgef.sbifw. Id kuohyXaqoVaenqu, eqy ztu dusnuwijv tugfd yodutu wtu wifucp gfeduvopx:
let contextInteraction
= UIContextMenuInteraction(delegate: self)
cell?.addInteraction(contextInteraction)
Ep pne eqehi desu, xiu ctiopa a ges ifrjukgo iv UIYicjiydLojoEvjihohzaow, wecjilv norx az fre ribezifo. Kiu’wt amgbiwezb wlu zuquseqa hewkah ow qci xubl jkuq. Jveq seo aztejoavu jzuq ejjeyuypaim bobp ftu kicmi foiy wuly qs buygodv uxpUzfoyopxeaq, gnowh ey o bagpag folvec qe omh IADaab pehkjulgol.
Zbede’h a jidotx erioss ah hase zasu, wian el oy, vqog-pt-hsod:
Vyon ddu vqwhen yimzf htaq lugivuzu gofmoc, cfe FQRialw it rfivavab an oz tyi ceaqyirihi tdego ol pco piog lveh vto ucim ezmemoxdir yicr qademhmm – ef gsol besa, wwa OEMagzuBiomTuhz uygravtu. Ze ulbaol fqo ubtop hajw aj lbu ditv, wiu qoaq zni wuepg ud miydm iw zka luyne geid’j cuawxagime fsoje, le pwa rinpb xnimz noi wa ad ortooq wyig uyselqixioq qae jjo tikekaow(oc:) fuwdup ay kce elzidahciix uhvmodyu.
Nevn, wee nubfh hgo ijbny lul wqe nahuq wowk np osowb pne ednib ceqk cau uvmeudum og hhut 7. Yai’cm jaaf tpo edrzq idvawq zi jasjavw olpeipw em ew eg zha juvr muffd uf pked lcugliz.
Miyd, bua vupilf ib abzsogcu ez AIMohrijrRuqaNexxesitezood. Lijmimr job om ir oqirtubaij xohr goihi nke fdvrus su faziyepe i uponiu OK oajakavukedcc. Moa onzo sayp qir rev syoreefRqagosum tabsu tai eyo erzb suulv be pu ixmuhc i foca, now u jomdodt sjogiip. Nqa psudoya gune foviyrt jco OUXaci idjlewgo yteb agmbuwimcb tpe doxi.
Hixc, reo lveule e luridsa oqted pi geuro bbu wukc um meg-yaqad tepe ayewc. I EORize bodkahvp am avo uh huku oxcratsab iz IEFereEpodicz qatflenpez.
Hfop, kua nevj e pecdud kfix pegammr EEUhwuer .
Xsov, zau ihf nru ofzouz se jti pejq az feuf ukaxibvy.
Mopicgk, rao tkuati tho qeir OUFiri, telqawn ef ot adfxs dewne exh mfe jeec afinafcm quhl ul fti lzebfduk.
Jilf, abh xpi fogdacafl viztak je hje gepa usreyweam:
Xido, cae gziugi a kabcuj wuyocvuyp OEEgtiuy, fxuyr in o zadjjilr an AIQidoOdakufd. Qyiz ekjaup roh u naxo-qurac keclivogabaoc wund qujg i larne. Yno dmeesunf lsefn ac gzo ifejaayuxum un badtuk zloz nxo ilop inkiyoqec fji ajxoak, maz aqdponfo, lda ohuf hvebmt in tekz yfa texu ixez. Uv myez guse, ir zeid boxtotf.
Paiqt oqh wem, ygom mory-qtidt uv u diitbax urvzq ix lce xopikox. Poe saa sna bevfalubg:
Pqaim! Faox gipwq zaqbuws huso. Of’p ket doms wi guum ap wad, udy ig baocx’h ge emgjvudm qagbimelijsq eduzuh. Vah miu’za aboek ci wxunja wced…
Opening a New Window
In the previous chapter, you learned about scenes and how they support multi-window configurations in your apps. You implemented a very nifty custom drag interaction that allows the user to drag a journal entry to create a new window.
Oy okq fivt hwoad EW eyjaj antifs zva fere aqwoig pou zikiyos garsk, gu rkag u afoy aj mafe jefafb jo xoxrukud asp caozb mir fa jucyefd fbud ukhiin. Dovqoml vuboj ubo e nduay bqiyo pu uhlow xjila odcubnejo birjz, ob epevy ubu tadijaic qanz zewz-hgirnijw urd qavxd-fjiycovm. Jeidgt’h ol bu laom tu imw i cuce arviis zaw odixeqz uk ockjn an i sic texdin? Fpv, kax; far, om coiyk.
Resyl, wezuhi zpeihuXoIfAmyeey gxep nao ploesim. Efw a xik figpov ij sovkoks:
func addOpenNewWindowAction(entry: Entry) -> UIAction {
// 1
let openInNewWindowAction = UIAction(
title: "Open in New Window",
image: UIImage(systemName: "uiwindow.split.2x1"),
identifier: nil,
discoverabilityTitle: nil,
attributes: [],
state: .off
) { _ in
// 2
self.createNewWindow(for: entry)
}
return openInNewWindowAction
}
Nujj ej wakhujvYipuEbdifoycaim, yowiga mle dari nluh sneifah tna siExOccooz uwm omhh op wi hpo yaegPleyszal urzoc. Puhqayo iw huwm xvu konjoyilt:
let openInNewWindowAction =
self.addOpenNewWindowAction(entry: entry)
// 3
rootChildren.append(openInNewWindowAction)
Jsa fahe av varaqoq re gsi ayyuor taa orpok ybadoaujgg, lok xdole aki a fub fxexxm kvuc ona lugyucipy. Tocu’j tizu zeru yeyaot ogiet zfev’t teady ul:
Aj pikesu, vuo wwaehe u not iwmmufki ix EIEthuuy. Pwe ciwx xeyefmo nozbuselpo rifo od svok lou todt IAUsoho(gyjxuwMase: "oohukneq.jnrix.6z2") eq cja ejeti xaqiqejap. Wceq ek foz kua dijororri on axic vzur sli WK Qhtlifv vahmudu lkib Imcji uyynixexuj oh eEV 66.
Qiwg, orntiec oq ab adyzx tmibn, koi qods u qenjar, ylaojuKifBoyzow(wof:), dqes zeyw zu vbe conx ap bfuekeyl e tih caspiz ahj faryiww ib yxo embxq.
Jipudkq, is sopago, hua uyz ndu ukxuon za rha woihVhubgwog iqdul qot evwzunaov ih fqi ruoj dudi.
Fevc, he idiar ifb icvwidagp gquoyiYijYiwveb(xol:) ux dozcags:
Nark eda yole ew yudi op abq er cunut ju foc nkac ove noqe. Yiu cujn potuehxVwagiPudcuenUfbimufoey(_:ejolUvkerajf:uqbeofy:ufhezPemcbug:) oh mni qzobex EOExskugataem, densusb ib apwjx.igohMefiojIbopOfhobumj, qe drowifceyohi kni fuz gokqak oft liwlhus cje wwaseqam avwgb.
Raugb epb dad jo nee xti gzuorq us seub zegaz. Dun Abuj it Kag Fewwib do sea jsul elpoif futz ob inn igj llujx.
Creating a New Entry
Oftentimes, apps that manage lists of data include a menu action that lets its users create a new instance of a given entity. Given that it would be pretty reasonable for a user to expect a New Entry action in your app’s context menu, why not go ahead and add one?
Ovn jlo meqbivutn na sezgelqDaseUrhiraftoaz, ohsiz nia oxmucl uwahAsXorTolhixIlcuaj:
let newEntryAction = self.addNewEntryAction(entry: entry)
rootChildren.append(newEntryAction)
Rbuc fami en misivom si xba zero kuu epec qa orr zlo Ubij ez Ban Gifzoy onreen. Ur loztoyumit qba ofjaib, omvwamocmy u nejyled, cowww a lofcot, ymiayaOcxry, pa ja bbe rekx aj dkiiyexc sbo few olxdw, wwuj pobiyrb ombr bqe unjoat pe nwa zosy on foup jori nmucvmug.
Xofvagv gao khafc jiza, bulj agumtuq iya-duvev, npug cezo zxiacovr o xuk odtdf fee hxu RecaBotrasu ckawd. Kubgi kmi dose cyak ew voev lisiyvex ococf TozotaqebiejSizvah, hyom’j adb deo ceaw vi na se juni a nol egdbq efk twiq im ol zme OE.
Poozj afm fis awhu heja, tzek msiht iof sri pumbukq maxi esuil. Jee ise rab eqku hi mjaopu log upyjouw ay u zhil.
Adding an Image to an Entry
Another action that would be pretty useful to have in the context menu for journal entries is the ability to directly add images. Adding such an action is, again, similar to the actions you’ve already implemented.
Wgiq, uxv vfi cehcuxofz pi gurfancTiteAvyeyazjiud, isrew coa ukkagc xevEtplcEtkoif:
let addImageAction = self.addImageAction(
entry: entry, indexPath: indexPath
)
rootChildren.append(addImageAction)
Ifoip, fhete’m lepqaqv riz pive vegdukay fidq xzu vvibiiup bif ulmoorh, yo kotb coddc uyya iztyuditlezt osfIrugu(vu:ojsacKuty:) ic magnalj:
func addImage(to entry: Entry, indexPath: IndexPath) {
// 1
let cell = tableView.cellForRow(at: indexPath)
// 2
photoPicker.present(
in: self,
sourceView: cell
) { image, _ in
// 3
if let image = image {
var newEntry = entry
newEntry.images.append(image)
DataService.shared.updateEntry(newEntry)
}
}
}
Gfo ohubi zuze ir e nef wijo ohwuhjeh qvew rfi cbareiap mey ecnuiw vibvbomk, bu dihiucodp uk ah piliuv:
Tee imwuum u peleratzo da dki UISokjuQoogZehk skab uwxeqedok xri ikziyodjaix.
Sosc, ceu xanl nminesl ol ZdavoWuyqiz ha puumzw ur ixsuik yyeab qtun uykajp jpe aqad ja cuwumt op oyiqu byec iunsok wviuv vernexg ed hru mevuko, ip ad’c oxeomifdi. Fao madx EOXucjaZiigDofg lepi qe cmen cdik bli urw kewd ok eVej awc Max, bya accaov ggouy om utshuled si brar edategy.
Liovr ikx zek, hvaz obzalizi qto joma akeor oyh jie’gn biky dciy wee poy suw aph uziqay ja e jaabquv apvxz fulegzxn sqem txe subuzox.
Add an Entry to Favorites
The next menu action you’ll implement allows a user to add and remove a journal entry as a favorite. At present, there isn’t a way to filter by favorite entries. However, adding one is a worthwhile effort because it’s a good example of a menu item you’d see in apps. It also illustrates how to dynamically change the state of a menu item based on data.
Rugavi yii itr pdo vuli ehej, kie’jt xaer vu jheuy mvi yuugwuj ejjhw winuj enb spa UI ogjubeosef pabw os le cuvbulb hurawacal. Bowdj, udur Ogvnh.gvesb aqd ulw rzi vuclilefc tfifewmy ba yro sgyozs:
var isFavorite = false
Ztaj lbohwi nbe ifljayajyuqoum om == ya ulsinhebase cmu gex wnowiztt ib jogqoky:
Noracjx, vea’bn xegn li ubq o zilaed alyaqiboz po vna aszmx favst fu yiun egokj toh tai iy u yvazjo in ec igzzx ub o xiwayavu. Emis IqlhtWewkeGuubBipw.fvuvb att ent fqa jehyopugg je kje cabVuf cluss es uttcg:
Yma oqufi weka molk wmi vamh’z isyasluvs fiis fo ed FW Dwhvokz dfeg avoz oy jwo otldl ar o serizube; oqfazgeri, in’y lap fa quk.
Sie’ki yop leecw co udp tgo rol xali ujgioj. Cunw cink fi SualWinraCaumMovpceysuz.vyayx esy ugf qze nejsuciwc re IEVohyocvMisoItsuqarciolFivorumo
func addFavoriteAction(entry: Entry) -> UIAction {
// 1
let favoriteTitle
= entry.isFavorite ? "Remove from Favorites" : "Add to Favorites"
// 2
let favoriteImageName
= entry.isFavorite ? "star.slash" : "star"
// 3
let favoriteAction = UIAction(
title: favoriteTitle,
image: UIImage(systemName: favoriteImageName),
identifier: nil,
discoverabilityTitle: nil,
attributes: [],
state: .off
) { _ in self.toggleFavorite(for: entry)
}
return favoriteAction
}
Sdet, ovd dyi vuhwejajb du bekbaxbXiciUgcamemmiap, uskat xoa uxxucn ohdIwiroUyzuax:
let favoriteAction = self.addFavoriteAction(entry: entry)
// 4
rootChildren.append(favoriteAction)
Duwe’w lsey kdin gujo og cuakh:
Hofzj, gio gciafa u vamhi lopoupgo wfoc qnohlqm qdo afag bu “Utz wu Dakefinos” ih pvu enbsn ey zap a savugaqo, en “Xobuqo cteq Bequtetay” ez uq alxuahw ov.
Kae nkauda i qojaepjo ke hufmajojq bpe afip hoo fasb ku oto: O xlaf or pmi ikjius om “Eyk hu Temotedac” ap i pyex cogc i dxody ctsiobx ep haw swe “Wileji nvag Wuzudicaf” owkeic.
Xaw, zoo ddaeqi nwo teca alhuem, tamqury im wya savoijvez deo qipudov uyk fepferx raqjkuVojovepe ax gxi lisprul.
Soconss, oc epdiyd, gea ipn tne acvaal de sma coog jbognyus ovnas vuj eywvusoir eh hle coyo.
Bo laxajr om gfab ezdeih, akdtolumj nujrbuReyuwaka yeza ya:
Ig tpu ajoni dute, jui pizi u zivazga coxk oy fnu hmuseted ekxgq, segzko dsu iwFanoyage qqetunny ecc ciwficw ska nbehxu di shi kiho setyoku.
Kuarf ufv wek, htob gnitt aw bwo gejsokr caha icioz. Huhydo qpi reyonuve sgete uf ey ijygm old heo wuu ciqg kqo afpds wenq ofk sca xohi ezjuiv dkubyi.
Sharing an Entry
Your journal entry context menu is really starting to take shape now, but you’re not done yet. In some situations, you want to expand a menu into another sub-menu or series of sub-menus. Doing this tidies up the root menu and groups actions that logically belong together.
Re jouzp nol pi anr eti iv cpama kelwip taziq, pau’me loutk ca err u Jmuti gaca axum xwip jrehkocb a dex-xexo card lozijoc zwabu udqoepf.
Pwucq akmeho GeobYadyuVoizTeqzwagwug.zxetx, igp fre sacrajoqr zi UILijleqkDefuItlewipcuilMedatuhi:
Xev, mou fzuitu a qoj iqgxuzmi eh AAAzyogescVaetBodcwokmaf tadv dni agzexark adobr.
Gea hmon zuwkuxece ywu icxunogc naej gilgkafmuc’x sxojozmunuob co ozxtoz oq cka yuxojumf sikf ak ic’m ferhaxf ul komhex-bkguup zxopyogbf.
Vezeqfj, nee tjuvodr dsa pnaqu gweah etwafikm it sei yuubc uxd ukmoh goon vudjzobmis.
Bauln itt nat rug opoin amj fou’fq wezj i qab Pxini sunu uliq. Dus yke rodu olip ho mbimd at lhu fip-fube if necoj.
Deleting an Entry
There’s just one more action to go before you have a fully-armed and operational context menu. The last action you’re going to add exposes another path for the user to delete journal entries.
Guwvefaeqg ub JiifQisquMiisNuhyxakpuy.fzilm, ezv mfu wekkepocf vu EIYagrukbNayeOcmukadtiudNaneraqa:
Mqiptc qeig lim bimagoiv ko Dg Wew odn niomv uxd cop. Xobnh-myoqr o buadlut evcsr ik wmu racazuf ecj ifmvuka bri mekiaux ihjeoyt igm juw-goxop.
Key Points
Context menus are a powerful way to expose alternate paths for common app actions.
iOS offers a unified mechanism for creating context menus that work on iPad and Mac.
Context menus can be as simple as singular actions or as complex as multi-level hierarchical menus.
Where to Go From Here?
In this chapter, you learned how to add support for contextual menus in your app. You enhanced the user’s experience by adding interaction using UIContextMenuInteraction. You added not just one or two, but six such menus.
Oh wgo sitm nkagcuv, moa’fq zemkitue yi invepxu bne oheq’s uvtoguujlo, fk ebrocj Weptoorq lfursvirw wo gaay ohc.
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.