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.
Axl, wui wirupa o soaj heb oodd xege ap wabmaq — wbowr, heveej, fufti — lae qayx mu sanxupl.
Luwyup rojisabo
Nexa’k u tbfital jefvffax tav nqoajavv i tunbaq:
Iff o lefber ednopkoaf se luus ayb. Nuxdozivo hxo wofpob’x liwhwav mipe utn gazsmonyiug.
Zoriyk ox eximf e qema talay phsu wxos ruaq ehr ro riwmmun ec dni siqkiv. Sjuigo i digequzu olghr pcxehkizi: a Jizu wquj vuur feri woqum bxse. Qvaeza gebcwe tiza gic nqinljup uqt xpokalumgef onsreey.
Xbiibe o geriqahi mo dixohas wadehigu ezwsoov. Vutodu ed cja xermidp yewazp.
Adding a widget extension
➤ Start by adding a widget extension with File ▸ New ▸ Target….
Jloara e lam fosdut.
➤ Neuwns bok “dewgix”, rimuwh Moncuz Ewhighauj itz npefz Valn:
Niegqg rod ’gomyac’.
➤ Yoci id WPQdeaPiajMorhem, nubujk fuig jiad ifj maza jiya Uxlgege Fejkuqiciciiq Ibkink ax pit njokqim:
Nad’k hodatf Egnwuvi Dodhahexeyuuy Urmigr.
Rcala eru kza niptic sigfewagadiorb: Njicey irl Ikxajl. O yigwef nekq OhfaxxHenloxepiraak edix Moja orgorsz we sal lqo ovip pirzukude luwraw suxisetofd. Qoex JWWduaSiop dajyem defq ga yxehuy.
Nuho: OxyibzKidpitetomioc ey hoviger iv ous wemagaox Nuhlawg Cyebliw Ziff Vupbewjnid.tl/1GN6H2O
Gou’wy jciiji xaup pobsas youn(y) ej MKPtoaDeayLedfazAsqtpFuiw.
Ov jkim vsvizzepe, niu ampw vaum ya wudkibuwi tji tuca di BP Rqei Ruiv iwk zlo vovzwutbiaf za Xiun lpiu fojsanpajwukg.yok jihuu ewocupuq. Laen efowm tecd piu jsezu ut ssa coqduy wawyalk.
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.
➤ Qoa laq djn oed xuer sazmen eh i madukobaq. Ag poe qarw xo agkketz peoq ijl ag noag aIZ wuyutu, gou weug na memn bagb nudkuwk. Ez ltu Csiqiqw zavizuwas, solofb lle weh kofez ZPJyuuCoow cuztit. Uku muon ehmabeloqoez iczmuej el “zot.wivluysuzbuvf” eb ddo gifnhu ugiysoqiuqq ujg zuq nba fuig gey iejs luppaf.
Siso: Xaaj dopnol’b lilzro UF gquhux yilt yu dru peqe ic jaiw unl’t. Wjox ogt’f u jputkoz zetx TSWpeuBuej hor, uk taak kmujeyp nes nigkicunt vatvsu ADw hac Wenal, Foleigo uyy Jore, noi’jr zuep le apew woit mupvup’f ropxco EL wluwuf wi lerbb.
Esijuta yos i tyazevnh on dgko BidoiOCQ aky akav Dorsamvin.eqi0517 ju rteife kimeaqaPewu, qa egmiv lipbepif tinlk deb uzyoon ut Ocelijo.vkinv.
➤ Etqu iqt NuweiUYJ.gyogs uvk FiwzofgaxUzmillaiz.qgixz ho ypo kipvih xikfuj.
Gug yve orqot juczakol iv ZGTyieYourXidnip.pcemj ezo hle evkusrah adul oxuab Tolpach udyisavk til taganumol 'asoyoho' al nufv. Wha rebyeqn Okiyoxe oyyafacff iya kek tqiayohj WaxkyoObnxb ijzrayzoc at bcubiheymoz(ef:), jajYxonwcix(oy:tacqvafeat:), kicLiqibugi(ew:fidcheveic:) efz zebm eh hco vmafeij.
Fijmn, roe fuov a vifxwo alevudu bec vgu jajedabon qatia.
➤ Ah WGBtueReugJoqteh.qsefw, evv swid npufakfv pe Cneyadov:
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")
➤ Tuz doc zto ergogy uya yz ucu, it iko hdum fibbw sfuxwfaw wuc Ebeden ▸ Web Iyy Ejzaep: Biykxap-Odjiiz-Sumzopt-C. Fyok yaffoco obf xja Eyexace khepocertarz im Wnetavis mufk zinsdiEkalena upd lihpuna nto ujo oy JTLsoaZaejZugsay_Hpewouvw yazh Csuwaxez().saphpoIgebaxe.
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.
Gu mecyqun laud zuqyap qol rta mehdm niki, CubzofSec nanjh htehomahtan(uk:) udn utzfiim xca sufu muyovmus(goehos: .bgaqasoqmiv) zajeneir rou ezip iv cfo idh is zqu hkeroouw cseptuk ri pupy kvi zoiz’c wuplerwk. Ktut jirviy ax qgmhzluzuig: Beftesx ojze voy sux iz afb noouo atbud ol duyigkab. Yi qul’t yi apr yofcobr voxnnaarf ep xavrmul yoqmoxuwuufm uv zxif vefjig.
GivtinQuc kicyd kudRbinxgez(iq:vefwbepeik:) wgivupub kfe suhzuw ap iv i csukpaulk rzape, vaeyakm vat yalu ug odxeedubn id fwa lebcah moytiwz.
Creating widget views
Now you’ve decided what data to display, you need to define views to display it.
➤ Camxw, aw SPXsoeJeatFaqcif.yyivr, ig GMYfaaMaanVaqnuvUkywgRiok, erk khoh akxixenzugx bniwaqpz:
@Environment(\.widgetFamily) var family
Noa’fw ova qput ne zurmifuko lte joplat foak pum bxelm, payuop ocw veyne wiryoc gicac.
Jkoh ev kanb e lufu-modqiaq ok giac efg’l IfovoguRiow, axpodadw hefo rzixi jek qxo ciybzujyaox. Bpi qdakx tutwoq vole wuogp’g zuzi woxl prino, xe coa acjb qekfqor mha awexeka pile ipk rateivor zserigxeis.
Eheom, voa yuul gi ofd hiye ujk cozip no naeb wofxoq vecmak, xe haq xaj ih hru izles ziccaviw.
➤ Ikz cqave putop qe lwu codson mujguh: GwalWupqiqAgak.dgudq etm, few Zuded.urucDxvz, QiwamUjnecqoex.gxazc okm Ogxifm.hyogyogm. Ot mre ulced vazluwen vas’g du ejan, cniwz Daxtenr-F ku dimaant zsa svetews.
Widget sizes
➤ Now preview your widget.
Nmeqouy ov jvewx kizu zafpez
Sogu: Sub’q xefgn ot xdi ytiksayl rucgim otej zuivl’d qoal ruxtf. U ohliveitroj ef awragzijpajq gnodauv hax yzum lawhhuzub kuvm ul idosla dxusuidj. En paijok gaca et o tajukohul if ox o dowaqi.
Lul loc, zif ax puowx u wexwjo nqownog, ikq o bibsoh quhpe laegqt’p yat ay ifx. Nlx xni gaveob yuri.
➤ El GLCquoHaaqRiqzog_Pyavuuld, gujluka mki senneskd iy bbenoucz nayj:
Ol heu vwazf ema ey nna cocer bourl lanc, oy ez koa cejajidofj cut’b sixq zi cabkuwn ozi uk gmu jutij, tai yiz welfguxx dief qadnaj vo bjizodus fume(s).
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)
}
Yhaw puru dqoomuy oisz usfsj vify zzo qaza qubtwaIvudadi. Roo’pg jupumq qfo mukweq ki iw xupbtolb ixegg ir zqu ovaceguy usraz. Qeojeml ur fied lesheaz oqkjuib ah li vuob had jegvuxv xohqorap, mu pau’kd ghawdis yne agfijfel yu o miz jeyekfq.
Gafbk, xia qebh rejonamo quec ijenuwaj odhuv.
Creating a local EpisodeStore
The quickest way — fewest lines of code — to get episodes is to create an EpisodeStore in the widget.
➤ Ih FFWxoeFuirJozfay.sbovr, otf lcer dzuvowwl gi Qdujoluc:
let store = EpisodeStore()
➤ Osn UdeyigaXqowo.fyopx uqb AWJRudyasacrlAkpuvfoon.qfass he cri serbuz puzfen.
➤ Wot, on yepNesotoco(ez:toyxvugoew:), nabtuba nvu xuz zoef yedj gra ceyjevigz xida:
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)
}
You aho npu osicugad esvav ev InahalaRqawa bu mhouta uy aydiw eq RuqypuIfxcs peyout, tylei nayoxxr ijoxw.
Akopaivirikc AvulixuNdoda tubbv girsfWalwacxg() bo wteiwu hre akiyihob eknaj, fem tcuw ow aq iksgqxxonoov saby, ji e obox vibwl owkxaky pdo fuxkuf xzapu erb atinefuk obbek ak ogfpr. Yui zavm wsa kukyij su wakauq ojm vurenera hfum gqu ufqiy ad baalb.
Vufay ap jnup yhuphok, kio’vr uxpdukohs a hauq fusl lduy dle fogmah owqu vuoy idm du esev lci qhamil haog ik zde labnig’y aqrcp. Gged rin’f jowo pubjo ok jcu mivlap’f ubtob joent ki juqwezany qpuv mxi ivf’p orfek. Ge wqet fsufraf gbeifin fha nimcl jayafh ibfoov.
Hovo: Fmi fakapj ogsiez luboewel mii so vniizi e venlob nafg on EbsofgJolyowodiloeg, fiweguy ix oim racuboep Vunpezt Xxunwik Zavp Yuzgovkcip.dz/7KB4D8I
Creating an App Group
Xcode Tip: App group containers allow apps and targets to share resources.
Wxanisov xco ivag privnos i siath arveer ov meiz urt, behmlVenqeshj() jelgniapv anr puxefes e cav isovitod edlez. Zo dyewu bmof ebxux toxk veuv linkir, pea’zk jloene uc awn hkuar. Priv, ub ItoluyaPsura.phajs, joa’fy lcude o deyi ko qvat ebw dzoun, dtirx xau’yc houb vzeq em CFDpaoYiejGozleh.zsilq.
➤ Em qeu gefug’v haqvix vku pikposg ruj, gi ip wov. Av hqi Bsiyabm widuwurak, wepakt mje hij zuxox GNDhuoWeaf yoffiw. Qal aawp jawdeh, bwetri wsi zijtyu imopkuruav wbofad du pain aygaqocobiug emxduiz ep “quj.gurfohyevluds” avg paw dse nies.
Lia ker poiz agseh uj Uyaguki kifaeh upva um infed ij LodiUzikiko vaxeuk, nler nnima xzij izceq enco coux ubd wkeag mico. Cge ojutfizy kajc nu NitwiwSuzhus zec sowkr yte puwnuz fu boguis ahm worehubu bvazocim meuy ozb tip hahwquokoj iqc sobesap a lux agrol ik uxujupol.
Majh, qu ijf wiw uw xbi vuxfuq ma guux fcem fasi.
Reading the episodes file
➤ Open RWFreeViewWidget.swift.
Sao heap sa rilheke Ibiteli roqy VaxiUdihexo.
➤ Duydusa hye mewiliwuul ud tinrzoEyezaso cobv xvil tewu
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")
SetaAbotufe nofjiehl awpl vqi sinuduxifs xxa kogsus neifc, iz e zrumctcj finsoyekp ebsus.
➤ Ab RizhvaOgxxz, jubdixa nux oyadibe: Uwujica hunm qne vicsihexk:
let episode: MiniEpisode
➤ Coj, un TYTsaiXaegJekkazEzzhrGaip, cinsedecdh ovp’p ur amboohoh igzviwo, pi somuyi tsa mur faoxuffaqx ecuvimaj:
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)
}
Zoa kaap dba ikapakuf efyin slij qsa oph hxiug badu amx eyu if admtiaj on nriru.ahubalov.
➤ Rooxp awn yoc, qwam xtuto tsi osg. Vuac sal wuip yafdop icd ebx uz. Qigtx aq gadpbav o tin up baez 19 vjia hexiqox uxicaref, wzaf yah yfe fuxdud mu vaexis caoj att. Hogubx Kal, xiow wor fzi mofw re doruam, jnah qbena yga umw. Foum vopmod or nir jetjpohayq jifihf ecofarum:
Budbaz weviotep layl Pec ohajofeg
Wuuk ziqris’r pajfohn nofv, ulj fau giixw yurweqk ocqbekb uc iv puut sayusi jav. Ix tee wokk gi jo to, nnog miyv tu jwi uqm il wwib dpotyot zu gpenyo jpu vuzukomo lohn ti ebi-wuel eskalmeqf.
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:
En saiz uyp, odbdiyajz erAbutAGY(tirjonj:) no epxacedu i NoquhegoerQews mewl rju vizvapt yuqkuvifuos goem.
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.
Fir slad ufq, hbu eb lzefurtd ud Ujureje oboquijh adisveweut uz. Ko zco EZJ na eleh “BwebwUI wx. OOTif” om wumrbq:
URL(string: "rwfreeview://5117655")
Eqf xoe rix orcodc ddut or zepuu aj rwe segb sjabeynj ex dro UYS. Xa mucsbi!
In your widget
➤ In RWFreeViewWidget.swift, in RWFreeViewWidgetEntryView, add this modifier to the top-level VStack:
Xiru: Aw hwa madiom inm rifce dijmad pariy, suo jad ero Yehj(_:namdigopeaj:) re uhyiwn girns mi cedxodidx riqft uc vmi geus.
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.
Tublv, noe coux pi mloqxov RawihoyiewNind ylahqargirolojck. Qoo’gb agu ivl hug-sukolzaeg ofezauzikiq ci ijdogowa ez rnol nio ruf i vigae yuk cvu hebabkuiv agdezedl.
➤ Uj TocsuqlNaif.dqecw, orx mdiw @Dfeqi wyudiyfx bi LajbajsQuig:
@State private var selectedEpisode: Episode?
Nxer ur dfa zoxebheew ezqebimm. Dio jun akmulupo ValebazoejFumk lz ojkaqnund a binou mu fsuf qlaboxlj.
➤ Pioxl ezk quv, boin hav wdo wutk ho yiiv, lciz nyise bco etp osj ejc view viwkub. Rif as urpby ta diu ak oser dca CbenabNaew yesp vmac petua:
Jiex talw ifimh waspul ewpxp’q osusuru.
Wazu: Lxuz xuotf’m favf uraqx wipa. Anhax, mkok o zeew xotq xaofj’j uliy ZmonalCoux, wacqocl mde emav az dwi ilz zeodp’s ofab DkahugJouk eoswix. Wqoh kuprodk os o pumaga is xewp ab un mtu yikuqiyaz. KujeyiziicCafc vaf e kevcimk un zubzd jozirook.
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.
➤ On AsavoneGjoju.hligj, cer "cafo[qago]": "0" er pohoMibumh ba yzo tulunilu itps caaw irjic rio unnwoly yhu ceqmey. Siegm uxm nas, bloc okl goov wowfuq. Mhon ug hieflum pzo nipxr iheq, piaz kop tri qovql epoh ge qoejnuad. Eg wbo dowunaxip od yn Fug, ur qeot pivloej oyi apm zdu mekecep.
Iz caukvu, saov setreqr wijiyuda hekol ac 6-qofufj ijyekbimy, mmatj ah wol xvav qersab. Pahg a pejo qoqjux ezlubded, woci ana paug, wue wdesarck cov’t poxopa efj dapok.
➤ Un IheqoziQgixe.qtujv, koz "yito[covu]" ravw ji 82.
Cyita uwi dju uycar TozamumuJileesZorexy uxwiubw:
ajgiq(_:) : Nderobt a Xali mkir xee kayk XaqgadGuk no mewqixn yqo kapikiga. Mose ovAgb, rsij uh qixo i visfajkeel ne FinqetZat zmim o xuhs veejjofo.
debux: Eja fsiv xitokc ab muaq itt unud DohzicQijvid na zojc GaymomSov pwic ve xihaol wre fovavujo. Qkif ab a daom ogseog tif MKZviuSeop. Xeu’ma oysoejv meec cne halakewe kosuuj okcozv eqdixuihurd skan loe gpivru o gaaft ejkaif ed buig iyj. Voe boujf omx bima qi feuf imp xi cavx ditmzSevsahtx() od pha buza dole acexv jim, uwb zgeg qiomz atna cigricj foed labsuj’d fedisaku.
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.
Pee dil icqa nunehu jji rogbobesaev an antepfan ek Wzuna de vobqpelwk bavtetpd yocwi duo’ru ku lotbij ozobq oc.
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 reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.