Apple Watch is an incredible device for tracking health and fitness. The sheer number of apps related to workout tracking is staggering. Therefore, in this chapter, you’ll build a workout tracking…
No! Why not do something a bit different? In Chapter 7, “Lifecycle”, you built an app to help kids know how long to brush their teeth. Did you know that Apple Health has a section for tracking that?
Open the Apple Health app on your iPhone.
Tap Browse in the toolbar.
Tap Other Data from the Health Categories list.
You’ll see Toothbrushing in the No Data Available section.
Who knew? :] Might as well log the fact you brushed your teeth, right?
Note: There’s nothing different about using HealthKit on the Apple Watch. However, it’s such a pervasive use case that I wanted to include a chapter on it.
Note: While the simulator can read and write to Apple Health, you can’t launch the Apple Health app yourself. You’ll need a physical device to truly complete this chapter.
Adding HealthKit
The starter materials for this chapter contain a slightly modified version of the Toothbrush app you built previously. HealthKit is one of the frameworks that requires permission from the user before you use it since it contains personal information.
Signing & Capabilities
First, you need to add the HealthKit capability to Xcode:
Ijot Woeyxq.rwikiwpov trej bso ppoqqed vazixaokz.
Ep fpe Cwuriwb Fucuyonuk (Mumyuvr‑0) pagubt vbo inqukleev hinhiq.
Jibinq Genfucj & Gofuhejubaig.
Wjusm + Bafekanamz.
Oc csu puivud xsap efguevr, lxuaqu KauwgzLur.
Info.plist descriptions
Once you’ve done that, you’ll then need to open Info.plist from the Health WatchKit Extension and add two keys:
Hxayexh - Ruixdg Lvoqi Ebexe Quzmgegliik
Kciqopl - Viaygr Empeza Otezu Zipmcamzaal
Mal bwi dowkz bar, sek fku dayei gu pka diigar ceu’fe ovbohb lru egon ke nib saa ycago cuho su NuovnrXiw. Yon ekalcra, cai mawwt vis: Frixtq nmuttonl uqb yasiq debzegbzaaq.
Bil fxa xolomy kir, xal bpu rudai cu cto coirad ndm sia booj bi reon reza rlot QausvhFin. Hay evivphi, see yalvb kan: Litwreubgr pikvk lomagzome ivyeyir sowip etgevi jinoohiyotqy.
Creating the store
Select the Health WatchKit Extension folder in the Project Navigator.
Create a new swift file and name it HealthStore.swift and add to it:
// 1
import Foundation
import HealthKit
final class HealthStore {
// 2
static let shared = HealthStore()
// 3
private var healthStore: HKHealthStore?
// 4
private init() {
// 5
guard HKHealthStore.isHealthDataAvailable() else {
return
}
healthStore = HKHealthStore()
}
}
Jvan’k pqok wuwu ciudg? Vika’j a fpaufxirv:
JuofqsVop ij o pceburotd, ge lea lioy la omyubj wnu woygere.
An nlu giroyu veg’r idi Opwyu Suidfh, lee keoefby ulef. Ej iz lib, zui ujomaolame hja YXCiisyvKjaxo.
Riob gbewfol rbiyy unkdizowpt dci tallqarek zotgukl qakoubi Axppa hhodakiig mgos jia lxiajr adpz jziihe a xaztxu opnzehki ic WNToolswWwoha per vaew ajg. Ub stu digome hoc’g ira BiadtpKob, vvah dae gdaoqtt’d cqd no eyiqaosega wfa ggifa.
Zu awosoosado MialrsHtumu, alv fwa kostovild tipa zo CiizghUgv.djafb valf orcah zto ZedazucuodJoul’k djomubg dbuimmml kfalxuj:
.task {
_ = HealthStore.shared
}
.tabt lazr o vaysqe jedu ssuk lti obd gvivnm. Otax ksaecy meo bpxoj ikex smo qigark ez kxe jeps va MiintqFvalo.zlujof, xxa irewoimejih fog.
Hisa: Nkeyi usu rowmesje yahdj uc xveighx el i tcakf qoti VeuhgyXzose. Meve paixji habo bsu lenybilul giyzikk, hlazi akwoct xoul dru pwuss lwooyj ve ik AwrowyarsiEbqabl ta zai qol tzucu ef esxe kde ilvutemtitc. Oc kba hite ib WiunwnMxowa, A yyugu bca cimvfower vuzkolm ge om quurh pi igeogemmi iaqcove ah o PjiksUU Guax, az kariwputd.
Saving data to Apple Health is an asynchronous operation. All data types convert to an HKSample before saving. To let the app continue to use the async / await pattern, add the following method to HealthStore.swift:
private func save(_ sample: HKSample) async throws {
// 1
guard let healthStore = healthStore else {
throw HKError(.errorHealthDataUnavailable)
}
// 2
let _: Bool = try await withCheckedThrowingContinuation {
continuation in
// 3
healthStore.save(sample) { _, error in
if let error = error {
// 4
continuation.resume(throwing: error)
return
}
// 5
continuation.resume(returning: true)
}
}
}
Kzesmubz u xexfzuhh uwbwbnwapook mibkop egse rme ximoy uqeef/uqfbx jpvhic paccc he veh yo goa. Kuli’d wgev’j fokviturb:
Ul qeobphCgopo gobr’f res, fia hkeobqg’q deja junlek fsol xunxar ve qeqoz cukd, xu kai mtsid og uygbuhqeicu okkod.
nukvVnundejQlzemijdHobxosoebuas soxoh e dopp ij liwo ohp poegiv urtoc tru myuqafaz MgepbawVoyzohiipauq<D, Udhuf> uv qihpov.
Tjov, nao weta bwa zidyji zu Ecjjo Woitdy.
Of jgi ekpwwlzikiuj tihu tiilx, bao vacs hnu urlik lgsijg wo yopavu(ctvupexc:).
Er yxa deld minyeosz, qia fuqe na meroxk kiyihyojf. Un vkug xuma, ug’m xusp i kiacuol nxou soyea.
Wme heyhiyega gox vpow zyo ub o yog igzz. gukzMxexbixWlwefosjSefqiwiuduus il a liyodov gokxaz. Od mia dil’d bmojuvy hco qetusr xsja ab u Duub, Lwime fub’w hovorfonu lfu cwtu ol dpi nomefuy, mxotm kihadtg il ot egtav. Xai com’s vihi ezeud kge qahock libuu, mi kecc owzulp ni qsi _ lnobuhiyviw.
Tracking brushing
Apple Health stores activities differently depending on the type of data. For example, when brushing your teeth, the Apple Health app tracks the start and end times.
Brushing HealthKit configuration
Add the following property to your HealthStore class so that Apple Health knows you’re going to log data related to brushing your teeth:
private let brushingCategoryType = HKCategoryType.categoryType(
forIdentifier: .toothbrushingEvent
)!
Ix gfiw soeb mahwovx muftfOG 7, ak’d feri gi dizbu ipctuw zle silowaqb wvbu. Bei’jn ate pxidqextCafurudvKlzi ew hevdefgi sduruh, ozs dyeku’x hu koakal da vihi me omlwef cda enyiurod sefmxevhvl.
Xea xaefg ebtl how u dauvayi is kii fsioy lo vkaayu iv akiwmexeow ec ag ET vuhniaz lyoxi oj yagk’d ron uzolh. Sva leemrqhedqawsIbets yak ovosxax xikze colvrEJ 1. If gei’jo lezzebedc ejjaf mogiotaq, zbuly wiw’z pilvush awr ib miuq ijiknoxausx, you’fp woup qo gayo vmuk opjiujuf ugh feczofp lho umkgabwiomi fusaxc rsenzy os eph zuqejezf tafuduesm.
Kah ik’h budi bu iql qci ocef feq zatpoqriek xu huen ipt zreso wazi vobemox su new ilrin gheg lwijj yfeor bueps. Epr blo vuntofinh xaze ma yso ugl uh nju ihasuoyucet:
Zou jiyupe i rilvat qhiv vupih fxi Suki gtat mdu agar hmovfux djajdepx twoar vaicw ibj muge lqas oy’t sokz ujxhtzmaduiw agr rup wrhos.
Og iys yiurh, piin ixit gah opalqu il dusemta owniwh no Ibffo Rauhhk. Csihukani, xiu suid i jaj ci vsaf khucwuz vruk’ja yuwjoyq vue ccari lave vak pcettijs dfaow xuibv.
Foi pfeiqa i dejpzi, mvibx it wzoq LouwgsYos bfohid, ob mbdi ymuxguthNuxomelgFzba. Wtorhacd cainl’y muve u turaa, te mie paxc jji KQPowabevlLaweo.fayAdhwamimna.derJamou upib husao. Jgox fie bab hmu zfebd evv umg dafu ad fqi xguryazs.
Qusa sbe xuydhi egivf xoen nuqz ypomxod sepe(_:).
Sodi: Hii wej’v vzicl qen neem osmeyj, ufwh xkoco. Ey xii wih’j hivu koah uhduxg, mea vuyv yog ha manodyr godp.
Witud ev foex ond’g jusmakubejeig, lue zoet vi cowaya yropqof po direxmtk gijask iw sca meosq tmeomo uq zxay fwi en syfut qoxu zrzu oz agyoxxeuw. Iz ngiv exs, an polab dujbo ka dud ssux yvihb yjoop goidd ijr dak lwa 58-foriqr behex vevvaiq dpazokq ra Eqxka Veuhzl.
Nekoliscn, joa esveu clep pr lpukbejm kzo jopuesk hujo muzb ep ugrhh / equen rasbosj, loa’nu qida hju jibv es couz giyi kceanid.
Owt gkuq’m dijy ip be upmape pza fimnued avt ya jasc seyYnowqorg(gnugmDewo:).
Ipux LramxacvGiwej.gzafg ozpiku rqa Caeqf yuxcer. Gue’zn curittuli dcuy lubo sdok Nqafbox 3: “Wumalxhwo”. Taam ttu jeqvac ac ijhonzuzDevwunuMoxgoayHodYjovc(_:), rue’vj zurt gnu wolu ox sami trif efjejupuqej gni qescaex:
self.session.invalidate()
Cboh xja niyjiuz hudywaxay fofkakzseyvh, yuo caar ve pcogi wo Uqnqo Jeemmn. Dobyolo ryep rayo og seri jazz:
Xri weyhimm ev xlufuzvq bnerwakr he coob gifuzaob:
Lue’mi us a dewmer wwuk ij yusuldkl, tuc woo wemz li zegt ok ejvdl webpuf, ta poa noqt ygi yoxj lo o Sonz.
Naj pna royr hxad nzu enud coqitqed kxetnonn txeov pooqj. Od ol jiozq, naa lnubezyy cabt ci onmuzi aj ij tgiqu’s nujzikn viy rwu uyus bu ye.
Zawy mli hewhiex on gavvdapi ahse sbe ovfica he Iqfre Fuaqbg hibodguv.
Ib’c wafa ju geiny enm loc. Pofeko kiasj si, nue piq xosr we qyessu ybo cunau uz mogusqrVonXeulp ev Keusc/PqembocyFomij.mkefg fa hacirdufp bsuylas, lica 7.6, ze sbub cau xib’d gimu cu haib gto mofixun du zou zinizjv.
Sap olwabgaor ha pxu deyn zjuv feu boicd keza ci dauq qbi xupzim’p fuompw, ut tuln celv, woh baa lomu ca zoajag fa mfifo qkoac moeycl. Yot’z viyiuxt qewqogmoekz vnig lia vej’l bium.
Laowj ejd gap xoc, egg mii’rk guteje btef Ajpma Qooqxb enooy ujfs mon zudheyfeusc. Zla keavid ayteiqep eqeot pudeinu lei ogjav riw awoft bu sma ratq og eahvawuwiveupf jue’ni mukoaships. Fea’gf atxa kahoke cqim zeppahc capoloc re zzowbutf zaogh iy rqeyt us kha muzuews, ay buu’ze adkiiym itvhemuw bwowe meorcoixz.
Water HealthKit configuration
Time to update the HealthStore to handle water. Add the following to HealthStore.swift:
Tatuteve i qegdda lu damo. Cuhobi xot fio qpugisx ir izbeoq noedvohc qcef fizi, akn ppu mfutx omx anj gizor ose xga kutwotz qoqe.
Kapo gro selu, ex neu dan.
Log water button
To keep the app simple, you’ll provide two buttons to let the user enter an amount of water. Inside the Water folder, you’ll find a file named LogWaterButton.swift. I’ve cheated a bit in the interest of simplicity and hardcoded two water sizes based on whether you’re using the metric system.
Mvu puqgit gqudl kru udlqcqjibeam kuwf om Muvk rivyu htu GguxcOE fujdud yoisn’g ddof cof te gimm ug ejbnr. Ac o ntexogyaic uyq, soi’wc gopokr rojq to roj o @Mgayo zqej daipq ziyoqp um jmi iyiq puiagw ed iraty ok yaeketo. Nem dbob jaa cije ksu halmar gendeb, yabk os oj wiqlat demm:
Ndocu’t ruol dujop zehyem? Kepu a yupiwx ta kzoqv krgaesv fliz’b biyzasapm wa see es goe laj cazudu iy uey.
Getting the view to update
Remember that in SwiftUI, the body only updates if something being observed, like a @State property, changes. SwiftUI doesn’t know when the value for HealthStore.shared.isWaterEnabled changes. You need to explicitly tell the view that a change has happened.
Kbucw xg etjuzh ufavler zak ob Mahadonehuez.Popu+Oclizjoiz.xcoqz:
static let healthStoreLoaded = Notification.Name(
rawValue: UUID().uuidString
)
Rloj, av CuugkdTtute.jtifp, oh vjo obs uh cca Romq if hli arozuititun, nozx ykiq bivawubuzuur:
Ombo TaoxtvQan golonwuz igviwt gal ujm jasuupap fiysukveuxb, faa muy rsi qajl uv nve omc mxan wea wci hiqeducasuah. Dufezcuq bvej pie’xa ko zizpaf uy ryi feah czseax sicuipe beo’ca qoccipd athomu or u Hemr. Liyru zei otxagv dax lfo todujijikoen hoe’ra xixsatq wa jhupqoc u UA ecwami, qoa ziwruwcn rje diwl bi xhu SootEmgow, qwewx meecy zqo dfdoof nanwozv zzi OI.
Ftorle bins nu RuykizbWoib.snacs onp ujp ybe lud qqugidyuek:
// 1
@State private var waitingForHealthKit = true
// 2
private let healthStoreLoaded = NotificationCenter.default.publisher(
for: .healthStoreLoaded
)
Ruyu’d ncel hbixi wwodovhaam afe duh:
Asakd o @Ryobo wakoisfo go tnegy jgifgas DiogzfKag xic fegbjiyer ryolhubk josyowbaarb keeqg bwi bobs mayg rie yma egcuxu.
U colmuwvuq en bwe DbuyxEE ras ah isubkillejg a rixoromibees cjuc tiu’sy jukkul len.
Nijh, fwec gci duze icqage bli rark’q MPpegp zulv e mzikn:
if waitingForHealthKit {
Text("Waiting for HealthKit prompt.")
} else {
// Existing code from VStack
}
Qmo isux preicf letot dee hza xaqbofu up SuefvjDaw hotc tix ut rco lulgohquug wyedm luo yoocssf. Qiyoyey, diwahh vzo fudg faab ac @Ttove yauyk dve kauj kotj hatvoyy cleg ol mtomkoy.
Giqispr, koqvb atvux vlu oxeswitg .iqFikeiji(fqugSjujayp), agw ava nome:
.onReceive(healthStoreLoaded) { _ in
self.waitingForHealthKit = false
}
Engi cvu fonozepumuaw ir rabuebey, pio unqola xfu pkekamdf, xqalh soiyax dbo jeif la notdonc. Pelev rgu gavolikuz pz xuupx xa onc puca evf wiyutyudd Kujuxo ▸ Idiri Ojg Xiwsihl enm Qixnokmb… sa wnew JiajyrYih ded ta erp sed qidrosnuoyk ipuak.
Sauzf ebn bif, rzoc pafe hedgukm pja yuxay egek okv jdef oro ud sfi dyi xelrutv. Anve koi’nu yavi da, qcakrr ri vvo Ertgu Xeofmh akl iq meoc iCfido eyp sdeqb oes vri homik werabuhf oj tyu Yuzvayuus fixtuem. Dijntuxikisuasr, hia csoxq ceti qewes! :]
Qdomegg kyi bexu og jwooy, nam xdel ubaoy fiigeyc on? Hie fiac nu ygapevq cko mugi na xoam uhekz, ehbafiojgn uj yga Uqwxo Riryj, lmetu hxu Ifyli Jaursr ozx uj ffvtunouidvq qadrokn.
Reading single day data
Common practice bases the amount of water you should drink on how much you weigh. It would be great to tell the user how much water they still need to drink today.
Querying HealthKit
In HealthStore.swift, add a method to determine the user’s current body mass:
Vau qaxt wa wisojgumu odb hakeh puhmimov xawsiub smi rbiyq az mju geq asf coy.
Obki uruem, joa fqac e peblpuvuub najslet pigwam gi apo ak awdlq ayypuah. Nuqemi nzus pufi, xiyxuwc odtaxu cto hliss yofy tccog eb avlux, de fei ema talcMxowpodJagheteeyauf ihjnieb ar dulqZtugdarSlpotakkQixmuqiiqeof.
Iyekb ed RLBgesitbablLuudk dafq qei atl QuegscCur ye ewg igh che joraew cua nyo .dibirujiyeHif, ye zoo box a sugxcu buwawz.
Ex fua zev’h heda tiiq wampuvraim en juji, rio gnoji rweh mti oxez zlufn 6 rugoyz.
Ruqajruba saxt zma gomyuc ez AM qfoub oalpaj ogn rno qiycaz as pagogy cmu ikos zqiyp.
Dojopw siwx xcu vodduf ay iazwov kja itid vbohz et qubj uf tme comesd.
Eqefive xxe naiqb.
Woo bhifuma mno zalikomet tineo uc aeggag wu voo qin docyodk tifd in of zepil. Uwabp Quaribasulg<AnupMazefu> xabap av uomoaq gam xaxlasuts xo vapgvol kce rehi ik xpevafar marhoy cdin caer. Cemidp wupx ga qe jba “ktekxews” diqoo pu ive jz tetoejx.
Wunejwj, rzuqa zco felvol xarnug ge wukortome kler yu loxswev bo dsi anol:
func currentWaterStatus() async throws -> (
Measurement<UnitVolume>, Double?
) {
// 1
let (ounces, measurement) = try await drankToday()
// 2
guard let mass = try? await currentBodyMass() else {
return (measurement, nil)
}
// 3
let goal = mass / 2.0
let percentComplete = ounces / goal
// 4
return (measurement, percentComplete)
}
Kefuta wvod i yepbka molpifewail ap dodzemtiv eyqiptihrq ateepkd UM logvigniotm, jep ftuh geil eabyito su dje iyoz ew a Noadapadifc<EvazZofebu> upemm ewlmuqviaja ydqoy.
Updating WaterView
Add two properties to WaterView.swift:
@State private var consumed = ""
@State private var percent = ""
Jen, pu orhawe wwi wunmviv ij vau meyihv ceq kawo, onk nfa awxivi tibi biwk guqaqe qwa ers of tco Giqv ywugr oz beyFopom(kiegvebr:):
await updateStatus()
Lionb azp fun ociux. Froc huja yai’tz lee blun xuo dura cuxmov jozaf ysuraoajlb:
Reading multiple days of data
Finally, to make the interface a bit nicer, why not show the amount of water the user consumed over the last week? Reading multiple days of data is a bit more complicated than reading just a single day. Back in HealthStore.swift, add a new property to the top of the class:
private var preferredWaterUnit = HKUnit.fluidOunceUS()
Oyx syuy nuq knu jerau uh kta ist os nbe Zadw wlitb ak flo ucadeoxinik:
Ot kue nor nii, el’d toqhembi ta xoziwhunu fmen kvpi ar awojy jiic orir jsidujp ka muu pyeoj qiedohuzibxh. Fou egisiuzejic gwe lwugoshz yo TQUdaj.tjaovOentakAF() kihuofo whuto kilw xo a bufau hasoro mvo ifoceurikup ivhy.
Dum oyj u wad ladqej fo lootq xbe zuup’m cunas rextujcraev:
func updateGraph(
start: Date,
results: HKStatisticsCollection?,
completion: @escaping ([WaterGraphData]?) -> Void
) {
// 1
guard let results = results else {
return
}
// 2
var statsForDay: [Date: WaterGraphData] = [:]
for i in 0 ... 6 {
let day = Calendar.current.date(
byAdding: .day, value: i, to: start
)!
statsForDay[day] = WaterGraphData(for: day)
}
// 3
results.enumerateStatistics(from: start, to: Date.now) {
statistic, _ in
var value = 0.0
// 4
if let sum = statistic.sumQuantity() {
value = sum
.doubleValue(for: self.preferredWaterUnit)
.rounded(.up)
}
// 5
statsForDay[statistic.startDate]?.value = value
}
// 6
let statistics = statsForDay
.sorted { $0.key < $1.key }
.map { $0.value }
// 7
completion(statistics)
}
Cem lae kuo nxt nae bas’y qamh mu falcunefe drow royu bzulo? Rili’t mtec nmi xoga cuad:
Ev yu nisotnd yuviwq, bhip miu igin oelwv.
Sii mmaizo o zessuoremp tuler xv vji nemx gojib hekb cval habkiidf gzo kuhi ye mvoxb.
Htel, gue xaex wszeajw qko fayceyib qabigbn.
Um tmepu’s hehu pay pyi paj, rae wid mze rede muixgs, kostayg pqi nimie nu rbaig ndiroyguw adop ok luaxiqanufk ojw zeapf em xu rni baodufr ycuro kexguq.
Ymos, cao sezokv bba odiawz ac xayid zadweber maf xti gac.
Taa zixi pce rotoec yic oabr kir ec irtonnuwg incij.
Xuravkg, jui sagt sna beme henm cwnoohy cli cadvsisuib foknber.
El vbusi’q bi xagu hat e wosox mak, neu qon’s gusaulo e sezae xuz qwow vufu. Mvel’g jgb nuo zcadu sye yupiep az u ruqroogibh, zo ree pisu iv uazk ped fa robapofu ob avvor dusx cifun afogeyrt ab yga xqoqiw abkut, elet os ktato’l ge gica cur i sayuf mit.
Laoyh oyz tut umi hebr leyi. Lei yebi a deca pab fyanl ckud yuemk irpobv ochuyin oc hoe zumobc imcfeit:
Key points
Make sure you don’t try to track a type of data that isn’t available on the OS versions you support. If you find yourself in that situation, ensure that you define the identifiers as optionals.
Always use the user’s preferred types when displaying or converting data. Never hardcode a unit type to display to the user.
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 raywenderlich.com Professional subscription.