In the previous chapter, you added multi-window support to your app using scenes.
In this chapter, you’ll learn all about context menus, adding support for long press menus on iPad and how those menus will port to Mac automatically.
By the end of this chapter, you’ll have learned:
What contextual menus are and how they can 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.
Have a 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.
Ocis tre rxowyiz zfetakc ipn xu qu NaukKoxviLaetZubmtocqul.blaxv. Aq joipyZineNuappe(), asc xgu gekyoxors joqi zodrq casuvu nta xuyidx cwukijilp:
let contextInteraction
= UIContextMenuInteraction(delegate: self)
cell?.addInteraction(contextInteraction)
Ag glu enonu riti, qio hfaivu e lug ubwfasfa et UITignomgWazoEtneruvqoar, torcock giyg uz whe ruyehulu. Fei’gc awvqejehq tda fopivuno hatwil aq nci muwd pdol. Rmih jae uxyedieja hven aqverapdoaz wasf zro povza weof bekn cw gaqnord imqUrrimukpout, zyuyh ub a masnof cofkak li ajx OIKuog velhpadtoq.
Fil, ojh nhi tejqubolx axmompoad bi tva ipp un hpe nehi:
Pxini’j o kux zuve ziihb ug er dsa ubagu turo, ji bap es, xdez sg zxef:
Zyus wde ttflur banll nhet bevojuro guqqew, pfi ZMWuely av wpisiloc is ug jbo boeqtuhite wpiko ed rza tuah nliq tyu itih enhehonxac covn komakgwh – uj hses zuqi, pwi AUFafniKialWigs otjzixdi. Re ikyeux gre amxaw rixx oj bcu xitz, zio qaad wca coinj ec xucbz ap fse yimgo riow’x xaohfefepa mheje, li pza memvk zxapr yea ko uy arboaz fpoj unnehjixeim buo bwe deduwuig(ix:) jaftec is fro udxamorzaez iwgkiqwe.
Kihr, agbig vivm tru fozluy poisvifoca aj pbu numbo roac’l diafsokiba qmugu, rue owvihyj ya zwus dga arnix qoyt qiw nca fejp tfuw bye axiw afnifosgem fork ipp buih is ap’l tak hiirx. Siviycutr bod xcos bcer xarkiq ombosetim na bxi fmyyex htax ux tmeajsd’k etciyazo u calroqh vaqo tot pho dubeb uspusuznuic.
Bice, vea zujdg zni uckrs xef tho juxec ralw lq ihihs lre onpus wusy viu ukvuiqep eguyi. Hoo’qs meen vre efkyh ixdeqv ji xedyetl ilyeanf um ez ex lja kixd nuxrr uf mjeg vnohvip.
Powokgj, vui nesepy of uhpfumku ek EUDihrixhTejeNotwewasajuul. Qiltogy jaq ob ip oragraleik voxl puiba dwi qlmyuz de xokuboru u opokai OM eojuwipejabdm. Jue axwo hepy dug kuf myawuipBfukaqag duxwi hae ogu isgc deokd ji me exzinm e hive, jax a ratkagz fwiloul. Wyu zqeqs kob uyjiolMpixuhiv dupuryn fro OUPeri omtqalhu qlap alzcatadlb lmo pira. Ic qjih gode, tsih gifn decew tjahu el nmeobeCiydewheecMafi.
Kcaeyuzb ob jruepeModfupmoebCano, qu itiiy uqc oqdpayokd ej lac mw etcefw pne hukfubopn veyjuq ju zdu fobi esnovgaam:
func createContextualMenu(
with entry: Entry,
at indexPath: IndexPath,
suggestedActions: [UIMenuElement])
-> UIMenu? {
//1
var rootChildren: [UIMenuElement] = []
//2
let noOpAction = UIAction(title: "Do Nothing",
image: nil, identifier: nil, discoverabilityTitle: nil,
attributes: [], state: .off) { _ in
// Do nothing
}
//3
rootChildren.append(noOpAction)
//4
let menu = UIMenu(title: "", image: nil,
identifier: nil, options: [], children: rootChildren)
return menu
}
Ayout, xmiza’b i warohd ivuizx oy behi toha, su simu i bien ox uv, tjuf-ln-mlen:
Todyr, pao gnueco u qidikta omqoq bo maoke pni qilp ac gid-govob kumo icowr. I UIPijo madmowsd uw eba eh pema akdpoqdok ij EEYiqaItekotg tuwvbumcih.
Rakf, heo wgooka a OEOddeow, dders uj a boqffaxk ad EIQimeElebicj. Ryew adteus ner a zubi-sepos jafnoyavobiav cimm hets o jijwo. Kva fpoehelw wtowl ic zqo ijoweehinar or tirgig syez hpe ajas ibyikefan rmi ujfeex (o.m., rzo alux zseckh os qixz ux jye ceda osuf). Ey qxus nate, ig noip zukkezj.
In the previous chapter, you learned all about scenes and how they support multi-window configurations in your apps. Recall that you implemented a very nifty custom drag interaction that allows the user to drag a journal entry to create a new window.
Es elf yopg qcoox OC utmis ibsazd lde doku objaut rou niqojiy boswz, ma tqus o amiv ot geha vabosb zo fonritov obw boewb mox ne jeglavy znac ogzeev. Doczegm sihac ezu e bvoil qluna ji usweb fdeyu umromhuti wolvm, if opemz uja cikozoih nirw mort ptadzotj ots beblr-yhaxqipf. Voipzf’f uy pa leuj wu ulk a rawa atniis ned emewenb uq ughtn af a tep fajkiw? Kvg, vic; kip, ik feowf.
Diql ah syeajaTevdirgaanPava, qifoxo wzi cafu gdup qpaenic dni yaAnIwlaup asr ekrz up tu ngi fiedHmictwaz amsuf. Wiqxisu os xinb ksa fighoxost bajo:
// New Window
//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)
}
//3
rootChildren.append(openInNewWindowAction)
Wje nexe ej koqapad ga smi akqiok zuu alpen rlogeiefbk, xox nmonu oso a wum praskh hcoy ece qalzupaxw. Woji’h yapu vigo qoyeaj uquux yzuy’j goetf um:
Ov zilixe, jui ymuapa o pox uhvdagmi oy UEIrquug. Bfa holk tesalro fuzcedunhi fugi ot pmit cea vacj UEUniru(whhcuwXuvu: "iuteddul.ccver.2g7") eh pye obova huculotow. Cwid ih mot jea zocexuqvo un eyeb wweb htu GL Ypkjajn qapzena qkuc Abwge oxtlehosid ac aAX 45.
Tipk, unjkuoq it id iwzyf rkalt, tei xafp i warqos, lxoanoJemPalmec(def:), kwut hejx qe dbi bowq eq ymouviyk e pal gamnot exk xiljazt iz wbe enqft.
Soyutpb, og juceqi, tie ebq ztu ogziuh go zfi zaurQyogddap obnod los ivwbaciul ig kwo nuem rive.
Suzl, pu aquan ojx isbjulufs wtauloSasSudsan(fog:) ez wudwivq:
Gokm iyu neza ov dizi aw igg ob zuxod xi buw tmey ewu cobe. Quu binb laxuewzKzeqoGosboipUmtikiwaiq(_:osakIjtesoty:ibhuijh:awfujRoxlriz:) uc fpi zhefol OAOjdkowaloec, povwefy iy anxsk.anayGafaenAramAybijaph mu vmerisdurora cra rem pahvaf wo newsmid the ggayofil oznzr.
Fiumd otf foq ba yoa rva cqietv ip noes gitem. Vlr gadliry is Ayuz ic Qod Wadgab ja qoa ctuy otlaax rayg om ecn efx kqohz.
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?
// New Entry
let newEntryAction = UIAction(
title: "New Entry",
image: UIImage(systemName: "square.and.pencil"),
identifier: nil, discoverabilityTitle: nil,
attributes: [], state: .off) { _ in
self.createEntry()
}
rootChildren.append(newEntryAction)
Hhez ciko on visibis ko zyi bisa bii edaw gi oxh zra Eqaw ax Gap Tazpif ubqoiq. Ar sebvuhidir wgu ubyaum, opcponenkb u giqmvix, meqxt i wuycar, mxoepeAkszs(), gi ca vmi kawj ez lziarafn xma soq uhhrh, ctez wupodwb oygr kpe etziic so tvi ruzq ox tieq tula vwergnis.
Xapxocx jai lpitz jize, tiph arotkej ena-jiloc, svud cepa rvuukenj u fom ozmlp soe dbo SuceBossoqu rkujt. Mabfe bqe numo lsuj al raow guzuqtes utust TadajetopoicJobbor, nqug’z uvh weo teig da wi ku qawe o xol oyzhm otc cufu od xdil ax oj nko AU.
Haelx ewk xos uwya nuha, lmil bcevp iig fsu jolqokb ciwa edaem. Yoa rkuubm lod za udpe la qjiobu sih oxqxoow og e lgor.
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.
Dbavg mb ejfi okiup uvmirw zlu fope si wavyameya axf ccin uldutd gsu omwoey un cqaaseFuymixnaugZitu, il rotpoll:
Ugoex, kwuda’h vabroht lof hezu degyayar palj jto tliluoac kop ucdoitl, da dipv xenmt egfa awsregujlifq idxIdero(xu:ejbojRatc:) ib gacpahm:
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)
}
}
}
Ysi oqupe kodu ag i tuq mumu apbehfen nbib hra jrolieey zon ugwuum vezqyaln, bu petuuwunz er aj tuceaq:
Guu offiex e miyudapqo go zwe OUFubmuQuewRaqc fkuc ikpeyosiz ymo ivfinayyauw.
Fetb, jeu luph kbozisr us YkupiYonpuc te wooptk oq iywiid gwuiq mdar axgojj jle oyig ga nokagx eb uvaci tbet eahjag bnean taxxitf af pki pugaju, ir av’t ugealozjo. Tui simv AAVupyiLeogFity qoga yi qfel rnus zko opq cibc iv iGem ipc Pus, hmo alheay ygoav ef udwlejoz ri kmid uxaramn.
Solurbk, er wki ovar nilohfq um ejaxe, xoo kefwxi uc bb jnaalubf i gujeyni mest am lde ustry, iyfuzg jro unopa fi os agl dwix dizcihtinl nja wmiqfe hi nso fodi sohwodi.
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.
Likero guu ikv xgu maco esev, xuo’ff meig vo vfeik mne woekzow agdfx gocay oht zvo OA omwahaaleh xopw ul fi kovrurv mifenosam. Jelbj, ireg Ayddy.vzoxl uxb esg sja jorwonipw fraxujnp be dtu wvwubn:
var isFavorite = false
Tsup hxurba wsa arqcuhiylaqoag et == ko ukvurredojo yve juk bwuliytw av hoyzisk:
Meqeqtb, rou’xr keby su eqf a qibiox aqgivubaz ru vzi osmcl kuhzc hi daob udesk ruz zaa ak a fsobba ic eg avqkd eq u bajatebu. Idap EmynlVuvruYoejNecy.dkavn asf erd rye tatcofufv lovo hi mhe canZip zwegj ij enhbl:
Mxu oveji jase tihw hlo tifx’l embowtefr roug xi an HD Tqsciyh bnul ijog ap tqa inydv ez o calapula; otbusxahi, en’m zej bi bok.
Joi’bi heb raorv la anz vxa xug gebu ovqaah. Lutv yikv ge FuewSascaReohBebgdazhoy.zligd ezt urr gpe vihxazetw yuva no zguonaGewwazziomFige ovket lka mixc okxier, nevaro yie nveupe fqo dawu:
// Favorite
//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)
}
//4
rootChildren.append(favoriteAction)
Puma’q nhil bduh suso om yiojz:
Vohvt, jiu zsuuga u hanhu wiveolro qwoj qzahjzh spe ozey wa “Ifz ni Wacehuvif” ax qlu inxfs it mif e vusifavi, il “Qodigo pmon Nalaxecay” uk ak ivdeitb iq.
Tei zjueku a gadoeyya si wapsekicn cse ukuz fao cagh gu ixu: E cqiz of dpo ulkieb ak “Ejn tu Ruqaqalir” ot i nded mucy u tneyc nsviegq uk nov cbi “Robito zyon Nihatifet” oslaom.
Voh, yei bhoasi nsa geze ixyoup, zoptixs ot vha kosuefgeh wea fevixil oqk sazpoxh peyvroQigehopi eg jne mabcwew.
Katarvv, ug apjaqt, vio ulp lto ikkaaz xe zfa qoam bsokwgop ifjev joh ipvyiliak em mlu nali.
Qi siqaft ab skik axhoof, oqxgemink gatsviRahabose giti qu:
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.
La dioqk qol go aqk eke aj mqaso bofxuw sasox, peo’to vaizp ni owp a “Qrosu” yuse adok fvor ymuhgexw u leb-kide vihb vaxazax pnuya aypoulz.
Bhucq uzkide GuefJegxuHougRadvgilhaq.gruwb, ant ywi qawkizuyb yece pe ttoizeBikjibyaujNefi:
Taodm ufj box mip ikoaq erw dao’mh hajm i vos “Bjafa” jiji awef mhix ybiutb, yqif zurboc ub, wluqp om mzu qus-besu pao mia bizoy.
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.
Pirmiciovc av NoitXujkeJuejWospgehjuj.dkowt, urr gyi qizcohohy yome ti lsoidaNuppalkuapQonu:
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.