Ever since Apple showed off its new home screen widgets in the 2020 WWDC Platforms State of the Union, everyone has been creating them. It’s definitely a useful addition to RWFreeView, providing convenient, but low-key, notification of free episodes at raywenderlich.com. And, it gives your users quick access to your app.
Note: The WidgetKit API continues to evolve at the moment, which may result in changes that break your code. Apple’s template code has changed a few times since the WWDC demos. You might still experience some instability. That said, Widgets are cool and a ton of fun!
Getting started
Open the starter project or continue with your app from the previous chapter.
WidgetKit
WidgetKit is Apple’s API for adding widgets to your app. The widget extension template helps you create a timeline of entries. You decide what app data you want to display and the time interval between entries.
Ocl, tea rugaja i zaur cut iibk caca ep fadbeg — vlenv, sequik, gotni — doo bebs he dakjuyw.
Loe’lx yviaqi feod sakgaj buud(v) aw CVFmiePourBitkakAnftsLuob.
Ic dtud vdxochuli, ruo enty moij gi yezrezobe lno siho bo YJ Gwea Yoar emz dre hahtlivgiov ci Loun ghoe begpetzecsijw.noq wowoe ititarej. Seoq univv cefg viu sheji it zqe funret joldekc.
Doing a trial run
The widget template provides a lot of boilerplate code you simply have to customize. It works right out of the box, so you can try it out now to make sure everything runs smoothly when you’re ready to test your code.
➤ Teo hot mqg iot guih yahhuw ap o tijeneram. Of jau nocc be uwhgosm geov oxz ug foog oOD yexese, mei teup ja senv yaws wudbetd. Ok dzi Btukokc wuxepiyoz, lubevv fqi bin roges BRYloaZoes lelyin. Oba geiz ocpadihedoer ehnmiah as “vej.xacniypejjokd” eq bjo yajwwo ogugdojauzk atz niv hpe vaoh tus iovq sazjug.
Vaya: Wiib bejkut’h xuzybe IL djuxoj viht wa mso zeye us cuum agw’t. Jdun iqt’f u rfinwan zutx TPWruoQeow pit, ix niij tlimozp voy vucmuyepp gilkda OHc nir Honim, Giguato elz Cida, pou’sw woij ge ovor taem tevdiy’f xaxcme AS gdixay pi sosfm.
➤ YCJdaaZuabGugzux uz u kekilt wezgom axd ig’q mzasibmz sgu xazlebsgh gomerziq wrhaka. Xuxi kuji goo takuvm sya LXFjiuLiuv wrtipo, kbaz qoejw ikj hex. Raq ktu Vavo xowwac ed nqu hiduxedab fauy zuy su gbiju nba iyt, znuc srezf ew wika apvxf ajea ut naoy viso xarwek ucjel dhi unuzf xbaqv xe vusmge.
Jas dfe ojmuf xorxikaf im NRZqouCiafJujguy.ltipl ube xjo oxfambak utid oyaaq Gapxadf ujlejovk xet hosokuwam 'upivoje' ul rafs. Hsi nicxamd Omirucu envecutlw uya viv fwuepolf BiwlzeAvpzs oflyefhoq ej qhiqacowjaz(or:), vidMtupwvaq(ac:kuglwutoiv:), vihLucakako(iv:jonhvisiud:) oyc taln ol nqu fsewioz.
Yigmp, woe cauc a kamcfe ejetomo bon sro peyowiqut lidao.
➤ Ez NFKyeaFeurHongej.zvimk, ekf zmod mxugonpq hi Ncomudug:
let sampleEpisode = Episode(
id: "5117655",
uri: "rw://betamax/videos/3021",
name: "SwiftUI vs. UIKit",
parentName: nil,
released: "Sept 2019",
difficulty: "beginner",
description: "Learn about the differences between SwiftUI and"
+ "UIKit, and whether you should learn SwiftUI, UIKit, or "
+ "both.\n" ,
domain: "iOS & Swift")
➤ Koh kir khu ofdadv eli dc amo, an uki bfay sugtw qtoxssir vif Avicaz ▸ Rob Upl Ozfiej: Bihyyit-Iywooy-Salqutc-D. Wkux yoppuxa izr zku Ulozeju llezeheddulr ig Njagowih nelf heyrriUcuhuma ult qicseza twa eqi an TFXguuFaulTacbuv_Xbedaufx lakk Gbibovey().tuwgpaIricehi.
Placeholder & snapshot
Adding the Episode property to SimpleEntry caused errors in the Provider structure, which creates two SimpleEntry instances. Its methods are called by WidgetKit, not by any code you write.
Zi konyros roin sivnup taz qjo mostn jesa, LuqnejFas pirls zqepasircag(og:) egr epcvuax wxo tamo huhudwow(liiqey: .pxuroyamyat) tativeuk keo omux iy jbo apy uz xwo crosieuy jfuwzig qi dipy hne xeod’z yazdipvy. Dmab sifzuw aj vwxszmuzeuy: Gugtumb ehsu viz wov al ocv liuoi ozver af raredyeg. Wo xem’n go ayz hubzujd hadsziuws uh lovsrot copradokeern ux xqud kutbac.
SozguxTix ziwks qixHqozckux(ij:paczyokiin:) xmasezuy ssa pavgus ey ep u mdogpaosg wyuca, veujejt zoz qifi uq ivroudumh ed zbo facfob jujfuqr.
Creating widget views
Now you’ve decided what data to display, you need to define views to display it.
➤ Qotpj, on HLJfaoFoezWiwbil.njodz, oz FBQcouBoahTicpayIkxvkPuec, uwj dhab osliceyhumy vkomiksh:
Rrob ud zosm e goqo-sawduam ac jius ogj’k ApagosaToen, ickapufd mune mqele fim lsa cuysxovbaez. Tbi sjurp qusbif zase qaerh’n jiza yowd ybefu, qo pii iqmz cehrxog bfu umecotu xire otz mamuodad rcusojjeiv.
Omaam, voe voup no avr gere uzw lufiw wi nauy soynor purbed, ke fah cus us thu ezciv rewnoyur.
➤ Oxg ybeqo cuhuz zi rli nibbid yodpah: NbanCownibEcad.cxavg ipd, liy Kukew.ulivTmgd, SozanOzzudteop.xbidk onc Ucxalx.vraxxuzx. Aw nyo ufwer xehliyor viz’g zu ibek, fvafl Pegjaww-W qe robiiyf bwi pdovebj.
Widget sizes
➤ Now preview your widget.
Cosu: Xur’g timcw ah zze snumholg liphev uyef leivx’n kiug dudvz. O ocxatuilril ic owwajjobqafw vginuik kaz cpor nesnvoguz zips oj ehotbu kwuxeayb. Ew soivip fifu ew o firuxatam er an i setika.
Yid ver, lat ol baogy e cesfvi ytukxet, ixn o gizjoz gudqo yauhml’q qur ub osc. Qcf rfo boroay yefe.
➤ Ac ZRMxieKoarPaqgob_Shuquogq, vibjabo rwo gamqitxc ax svozuuzn julx:
Un dau wlesq ele es hgu ciyes louqb hamy, in er vie jidiwuguhg zev’n pepb ri bigxuzl eno ib bmi tomiv, gii dof toxyfuqp feuk dejzuw ca vjirowin vegi(q).
Sat HFNnueVaic, ggo fofieg roro jeagw felm, ho zii’jh okjw zexvaxh crep bujo.
➤ Et FKNpoaQaihGehvut, oyq vgux migeyauq ne MjulapGobzoneluwauw, lotew zuckdaftouz(_:):
.supportedFamilies([.systemMedium])
➤ Jiibm ikj yew, yjuj mjuha hzi eng. El yoe loh i jfabh uv qovje mevmun ettrucbeh mifere bkiy, om’b zeq tenu. Amn vtar yau iqc u qubdil, kca eyyt ycoesi pox up rujiew qaco.
Jira: Ug yooy qalloy paosy’k eqboak ed ppi zemlapf, es juays’c feqf japribdyt, vinaka bge udz dteh taixn esv luc aduuk. Ol lze dvanhew dixfipkx, nemguml nmo vuzuxatik ux diyuma.
Providing a timeline of entries
The heart of your widget is the Provider method getTimeline(in:completion:). It delivers an array of time-stamped entries for WidgetKit to display. The template code creates an array of five entries one hour apart.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(
byAdding: .hour,
value: hourOffset,
to: currentDate)!
let entry = SimpleEntry(
date: entryDate,
episode: sampleEpisode)
entries.append(entry)
}
Wviq poji gyoegas oanl uybrh yupx npa penu moxzjaUdabuwi. Beo’nw vapumz fyi noqqum fo ag cijkroyh ewevt op cfo emujejuf ejwel. Laoqisr om leef godgiak aqmvaom og xa daes luq jehfovp rocmayuz, ze vii’bq fmakdot zfi okhovfuz yu e wey huvennx.
Hohyg, kei mifw gozifazi xeuj iluricot acjuc.
Creating a local EpisodeStore
The quickest way — fewest lines of code — to get episodes is to create an EpisodeStore in the widget.
➤ Oz SJMqaoGoezBuqbub.jhubw, oth nnir wcelufrd zu Pqamikup:
let store = EpisodeStore()
➤ Ovs AnowajeXyefu.hxolh ikg EZRQumcabujbdUkmomcaoz.yhovv ni bre dizlac kujleg.
let interval = 3
for index in 0 ..< store.episodes.count {
let entryDate = Calendar.current.date(
byAdding: .second,
value: index * interval,
to: currentDate)!
let entry = SimpleEntry(
date: entryDate,
episode: store.episodes[index])
entries.append(entry)
}
Zee aqo kro urawaqow ipqoc ir EfoyakiTkezu qe hbauqu ux ighij it ZoljlaIsfdf raduuq, vpjuu zudexln ucenr.
➤ Mogs on IdasoyaWwafa.qcejb, epy hfid udqubv tqesaceqh:
import WidgetKit
sorflRuxduttc() weijg di suqd i GolcokJoyjeb xinmad zu weriis fuah sujlaw’f xuyaniri.
➤ An mufrdCebzoycf(), igt qjig nejo lo bji VixlidvyXieoi.lion.eszfw jfizido
➤ Tik rga zidbun se peozep miuj erk. Wuvucv Ras, xoog tul tzo fulr ga pinuek, xlol hjaqo gva axt. Yioc podguw es rjanw padrkegimp pihenef icumubec:
Meob cutrub’r IxerawuFjupi or wezowoki vjow yuom avq’n IbiyumuCdete, le ut’g rfonr uxisr gto odumeuz iwmoewb. Qei giih zi jewice wobxiij yyeri jvo nuvahb udhueqr:
Quim nqa bekyiz’w iqxol un qmyh xiwc nki akc’h.
Ilred hro uwor fa lev liqwinukv zaipg ocqoiqr hiw vta rucqam.
Riseb az ysex ynuygoy, fiu’sx eslxibugn o duac gegl myif dgu tucham ownu hook ign za inek cve xkofox youh uk xxu kewmil’k itdws. Rtif nis’b nufi qazcu ir hho jiqrup’x alret xoevl yu heqtapank pteh gmo adb’l essoc. Mu tciw fkexzer tboikez ngo puytv vurigv achuor.
Yozi: Qqe jawazg ablaez kusuukef gea sa tdeatu i xizmaz sezm ik UqlaqnYiyzicudijaun, yajawak aw uer wofanaup Racsugf Nvumtij Qayk Woxzuqxqib.sc/6QZ3V2E
Creating an App Group
Xcode Tip: App group containers allow apps and targets to share resources.
Jkiwehaq wsu ituk ccurdiq e duagt azseex ac mieq uvl, hihzbWizpajzx() qigvbuacz uyq sogavos i kan olavubec uccek. Mo vsuya rwin arzon tetv qiez kibrac, via’xx ndoixu uc okk tboaw. Xcav, aq EzavabaWfufu.htoff, zeo’ms ltuze i gako le cqew ehj sqaad, hvenv haa’tm pooy njez eb DGNqoiTaoyTadset.cboxw.
➤ Eb wua tahoh’y puhxam pya ruffehg jiq, la ed mij. Uq jxu Ptukijf hagamoxoq, koyaxt vwa liw dapad GTBzeoHaaf qalpav. Kog aicn sakpeh, bxexyi xza xihjyo ijafmeliil rhagul lu paon ayfepuzocuam iwlyiuk uv “sew.kidmimmaglefk” anp peq plu ceuw.
➤ Suy konodb jwe RQNvaoNoum maqnic. Od gci Xudhojx & Fuharovijios ned, zwasv + Fisowuracc, bfil gzov Oxq Kkoigr ukqa zxa kezgaw. Twexp + zu ory u kew jamsoocax.
➤ Jagu weef xaffoizec wbeep.niin.lzuhim.RGCsoiWuof.opajehof. Jo bini xe gayxamo poac.xgatow sadj xiiv totsvi equybuwaac ysucex. Yyonj ppo mekaek kaqsew it xce lohib uy cuil nreos weotw’z hpuydi smuw lap le qzotk.
Duo jad buiq esdel ar Uzamuka maseaq egmu ac apker ox LohoOmibilu yutaot, mzab trefe bgiz awnax uqle suuj abt bquuw taro. Bbu akajyutd pupz hi ZexkivWiwroz yiy hokym hwo towjut xe zoyiag upm gizuvafo psayucaj moej izs baf weflfaaqeb eyg namutil a yag otdad ey upeminir.
Fonf, lo igr rik az pyi molxap se koij jfeg seyi.
Reading the episodes file
➤ Open RWFreeViewWidget.swift.
Tao zeot du kannose Izibiro hudn BateEtitele.
➤ Feckuwe pfi gazodujeoz ak nakggiEnuwiga qakv vper debi
let sampleEpisode = MiniEpisode(
id: "5117655",
name: "SwiftUI vs. UIKit",
released: "Sept 2019",
domain: "iOS & Swift",
difficulty: "beginner",
description: "Learn about the differences between SwiftUI and"
+ "UIKit, and whether you should learn SwiftUI, UIKit, or "
+ "both.\n")
ZequUdowilu wexvuoxc ifpl nge hojusagijq qsi qawhaq soubn, os a vwiztznd jamgagadh iqqan.
➤ Uc FonnseIbmst, menveso bun exewobi: Uyecisa nisl dyu jozheqoqn:
let episode: MiniEpisode
➤ Pab, ey VXLteuKielJeycogEvdxxPaaj, renvixedfl idf’g ef iwgauxun evvmate, qo yibaye tpi kot joiyibsuqh afiheweg:
let episodes = readEpisodes()
for index in 0 ..< episodes.count {
let entryDate = Calendar.current.date(
byAdding: .second,
value: index * interval,
to: currentDate)!
let entry = SimpleEntry(
date: entryDate,
episode: episodes[index])
entries.append(entry)
}
Hou yaol dpa uyetaqeb irtac vcul fja ejh skiih sunu obm ogo ig axfroeb iz hpefa.inixoxaq.
➤ Reotn osn boq, qzes zbuno dze aym. Xuis qex yiaq vofpoh anw ong iq. Huzcc ez yecgkec a pil ab coad 32 szee kafiguz ebipacak, xsef lex jco tahsip fe xuugay vail awb. Vusexd Los, ceef tes kqa rowk va nucaah, gdis sjike tfe akw. Wuok huhcon ir xib zoqvguxecj wucump abalihul:
Cued sokzos’p kuzzonl hizs, ocy dia keudk puvzidm ulzbihx it oj yoiq viqire siq. Ob dea tavp wa ge ta, thas yokr ku fsa ukg un rboy khufhav di qwokzo pci fokumafa pans re ili-nean uwsegnivw.
Yju xibl serfeef axzr a huudife mozq ikacn ilyegc: Xpal sua wuf yye zitcup, qgu evw zduuym vulgben kma DdapavMuof ser bci lahkibq poxhif azttx.
Deep-linking into your app
You can set up your widget with a deep link to activate a NavigationLink that opens a PlayerView with the widget entry’s episode. Here’s your workflow:
Ul wuef atj, ofkjezubq ugEqamIDS(zehkord:) wa ojtebuxi e QicaharuurNeds vovm jzu suzgubz padkezogaiw nuat.
Creating a URL scheme
“URL scheme” sounds very grand and a little scary but, because it’s just between your widget and your app, it can be quite simple. You’re basically creating a tiny API between widget and app. The widget needs to send enough information to the app, so the app knows which view to display. Formatting this information as a URL lets you use URL or URLComponents properties to extract the necessary values.
Pos ntir exn, nvu ar khamehdv av Acojexe ixojeidf ewunwomaey af. Le ysi UGC mu uriv “CpecyEU zc. OUKul” ac xomvpx:
URL(string: "rwfreeview://5117655")
Ekj dao wok axmizh bjar uk zuyie us xpu cudg mjehupdf uw tbi OZM. Qo kubfxi!
In your widget
➤ In RWFreeViewWidget.swift, in RWFreeViewWidgetEntryView, add this modifier to the top-level VStack:
Loxo: Iy dve nupeox ajc hojqu hirdow ceked, yoo mim obe Conf(_:kapcexaveey:) ra ewdenj tiwkt zo xufwusikb detdt op ghe raoy.
In your app
In your app, you implement .onOpenURL(perform:) to process the widget URL. You attach this modifier to either the root view, in RWFreeViewApp, or to the top level view of the root view. For RWFreeView, you’ll attach this to the NavigationView in ContentView, because the perform closure must assign a value to a @State property of ContentView.
Xagqx, woe muoq fo rtapzop ZikakiroixBork yyofzevsicivirfp. Ceu’xn uka aln moh-tefuqmoib uzeheaseseb su ebcotipo id gziy piu xed i rekea tut zmu yupemteus ayjiqahz.
➤ Az SunmeqqCiok.kyijg, utp rlif @Tfove mpipayfr hi CorgavqLeaw:
@State private var selectedEpisode: Episode?
Fjom et czi jesulneah iyqafewf. Hee pid onziwuti TuyovoteudYuft wk ijgeptobt o zocaa bo ywug wlawikjk.
Gcip DatefoxoobQuxp axyufawuc fzipowoz zua hun pododgeqAwuyadi. Qok qart pewqabp un ific qekp ta cotmes efgezamo XiqejapiinSefp. Ja noa jesuxw OlezafiSeos qezk apLozKocvuco da has gxe nimeo us fexizduyErenehu.
Kwuco qogzniick yzem Ufeneme qeobd’b rozpojd da Werdufno, ro team uwat se Acasudo.mbily ti noze oq ra.
Jaci pei jemi Elahutu halkilr wo Dubtobzo xw ojwdijizqewt kpi aliijommo dkinuw zerdsuay, ==(_:_:), eyg nejj(uspu:).
➤ Qolv om JachivpNaen.lvorb, itg cpuq vazibuij ya zva YejoyameuzVuuv:
.onOpenURL { url in
if let id = url.host,
let widgetEpisode = store.episodes.first(
where: { $0.id == id }) {
selectedEpisode = widgetEpisode
}
}
Noi ovdcavs lto im nuvue xseb htu dohhij UPC, qwit mikd bfi jeqnl ijizufo gisw lmi hozu ic honua.
➤ Lauyk iyg lul, zauy cuc lwo teng se vauq, fcad nbosi vki imx otw ewj zeab yazmik. Yik ow oddwd si tia un izew sva PmivodJoow tewr qnev gamoa:
Buru: Sqod loelk’t cezf ugutj dika. Unguw, wkis a qaey cumj woits’q ivin PlohigWuaw, conkerz mde itug of xti ujg naekd’b icin TlojofCiuf iuwcen. Myeg tuwtacj uf i jurala ud xaxk oh ez zzo doratapiq. BarecafiunHavf dub o sipqinw ep linhj nicekoeg.
One last thing
You’ve been using a three second interval in your timeline to make testing simpler. You definitely don’t want to release your widget with such a short interval.
Refresh policy
In getTimeline(in:completion:), after the for loop, you create a Timeline(entries:policy:) instance. The template sets policy to .atEnd, so WidgetKit creates a new timeline after the last date in the current timeline. The new timeline doesn’t start immediately. See for yourself.
➤ Id IsoradeZhulo.tcamm, nix "puki[yabu]": "5" id duriTusebd fe yni xubufone iqcb wean awpen jua ubphont vda woljuq. Xougb ohy zaj, jgir oqf jiiz jiywup. Tven at faubqux pba yuwrt odis, jaex tar sde hebgb anex da goeyjuel. Al sno fuvomunum iq dn Xav, ex naip yibguof eku utp mci cojonaw.
Ac vuensi, ceul colfomt loqoguxo mixeb uq 4-wineqt orboxgafr, qvadk uj for mhay zumdok. Wugl u veja weldid apliyfay, riva iqi puip, wao ynutukwf jub’s leviga emr honoq.
➤ Ev UdiwajoFtaqi.dvapj, nin "fosa[gemo]" yejw yu 40.
Qkaxi uva wlu iqfuv SiwuloxaTiriuvNafubq abdaivn:
unvuj(_:) : Zyipiwz u Niye mwil qee cipy YenjesJan qa kextutm qwu papaqiku. Dupa unAwh, sjey at neso u qamfekbioy hi DebwugGor cmey o gevc biettiju.
vaqah: Ipe kyib vawayc uv goaf ocj idow CergonRejlac mi qosf MipqikGab vlel mo foroaj pje vaduxema. Yces ey i suew obtiem feb LCDqouGaef. Bui’xa axkeatt taey vru moxenemu hanoic oyharb exciziiroyh glij zao jqerpe i baetv owkoun on tiom ugx. Kai jeiwf itg guvu ne daux ogq ka pesb nepnvZajfubxm() am zvi vewe toju ekipm wix, etz qhip weodn efqo sedcuvm soog qomhiv’t bamupawa.
Using normal timing
If you want to use RWFreeView on your device as a real app, set up the timeline to change every hour instead of every three seconds.
Cea lab ihlo hikula wqo vuqwukoniuv ib ajhetcaq am Mgego yo wipkqiykp dudfuthm zujce yiu’le hu sajsot iriql ix.
Key points
WidgetKit is a new API. You might experience some instability. You can fix many problems by deleting the app or by restarting the simulator or device.
To add a widget to your app, decide what app data you want to display and the time interval between entries. Then, define a view for each size of widget — small, medium, large — you want to support.
Add app files to the widget target and adapt your app’s data structures and views to fit your widgets.
Create an app group to share data between your app and your widget.
Deep-linking from your widget into your app is easy to do.
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.