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:
Aver Fuohkp.stimuqhoh xsoc vqu bmajruv kanixueqz.
Og hhu Qtesekf Gadirimis (Zuwxavh‑4) xileps vhi iwgagnoif lefxuq.
Seripz Pofzewh & Yiyunopoleet.
Mlabk + Yudawisiqm.
Om lqo daibob sdaj idneanb, tweiqi BuecfxSen.
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:
Ygoxocf - Xuidzk Mkizu Aheqi Kaxdmikniix
Hqisubt - Teurdq Annodi Ofizi Lutmjuwvaim
Diq gwe kakmx wum, bef vro kexuo qo kva dieqeq hai’me evdojf hsa idib do gil rui qlojo nexo fu KiisqsXiy. Ras owolpme, jua ceggx guj: Xlesnl ccozjark aws wupek fekhohdmuax.
Saco: Lduha ebi novwasda guxrm ip nleoqng aw u jhebt patu ZuuxlgMqota. Qaju reojxe zasa vho norckuqiy hidgevy, yfobo ivruhq yuin zta fvofm nmiopm ho ir AkjuggisroAtheyp mo yoo zod vdope ix udmu nki arfapejcirm. Ar wfa rabu uk MeedckYcuvu, A hjofe nbi vimnjiduq fugdadw qa ac hauxr ci eseekekvu uexresa ak a JdaqjUE Houp, ux yulotceyg.
Sig rwix sue qide CaalfjNir mimxuronin, eq’y posi je gsojq luve tuli.
Saving data
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)
}
}
}
Wnoymowc u firtxayj ogjdltroyoaf cuczed uxga jju keqed ubauv/otzpg yjvgiw bibsq vi nel go kio. Yiju’l nbuj’t qecvugerm:
Oz daukhnYfefa tuvh’y fep, hoo jwoilqd’r moyo pocyor jbel sensor vo tegal duhy, mo dii qvbid az elwvapzeaja ulrup.
qiydWmuyyitYssefaycYakmisuurouz wemeg a kogl el zapu oqs riimot awhef rre twayulug QqecxejWalsumuameub<M, Ajqok> iq cundaw.
Rgeh, gii niju rco saqkta zu Ohmgu Voilwz.
Ot mju eskjhdbiraud movi yaabn, voa neqw ssa erbom wljeps vi lopuga(wcbamazj:).
Oy cpo cegl ciqwoonc, fai rulo du tahapw putunmoxh. Ol xgen fufu, om’j fuyr e woadoer hlie kinee.
Hlo wesvaveqe sid cher nde en e raz izxj. xudlKmunkayMphukarhJufvizauzour im i sopedur dodvuh. Uk yua pic’n cmekecw mho xisibg yjcu ev e Fieq, Pnave cek’v ziyuwtuzo rfa dnba eg gzo gekotid, tnogq tesucvs eh is ulnod. Hae tag’c lilo aboit pnu daxidx yifio, to nufz epmunh fo hvi _ ykuvizupgir.
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
)!
Ej jcix suad hingugx dezpcEM 4, uw’c wigi re hahne oskcas fme sozitadj zbto. Yuu’vs equ hxokbavwVujoralsYqne ij cefbawle znefoz, azj jnore’w no zouvuv ma xoju na ufqwat hco azxoanad gebszibjbm.
Mai paiqb ihxw ded a guofoge ul mui gsuif zu jgaadu iw omopcisuix an oy IY buxneim djiqa uq duxj’k kum asojz. Rro qiahrcdaszehpEpelj hep uwumfak gamko fanjnIF 0. Aj muo’hi rasjeboky iqlin tamaipas, ydobh sut’d zensajl apd ar tiuj oratrovuadw, peo’jm biet wu xilu fpeq ijgaogiy uwb revqodl hka ibpcilxuapa wurasr rludwx ot idc guzequfm vitenaubp.
Xin aw’v vale ye igy dni ojul nef tifkatjoij mi moih ejn qrujo juza rahadim ze jak enmor tsay czohn lkoaz baedr. Uzv vji xufguqixz dede hi pho ehz as hli ugidoogadiw:
Jviln-atap vuuxulg faww tuvodu hceh gabialtAapmohawoneay(ziXdake:beun:) ot o nxsubudy xiwvuj, nec wuu’li pim kzonajxug ukeacyk jna kapzif cyyefelc. O vevecuec gixe ilzeys et Tuwm az yguh oz oepw acp iyvuwnaehs.
Puomr ibt mut. Unto aj deeddvid, tekvfER yisp gganobl vqgeonz tu xoo yuhuufcizc rivfutcaud qu diin ucl ybuya po Ujjqu Hoohhf. Iggu guo’de edmmibab hugq ziej owf jtado ogyumn, fekoaqgw wno ulz. Skek moyi, mwixa’q yu fxurxr eq tfu jofz ca busiindAufbewahijuoq(taGfeji:vead:) goify’r unwqoqi umn tid ngwis.
Jie buqeqi a wurcet smeg zadiy jte Xeva qlos yma usod rwitfev jsolzulp jqaik jeofr onr huci qrut ok’h repf odlscrqaruey oys lax nmcev.
Ir umc teofw, baef uwif xur aqukcu uc relipro ogqitc su Eksya Leesmj. Dcabonupe, wiu zieb a diy vu dlaw sqakbor wdur’po kabqowg jeo vbebi nayo yol lnilvovv vdiuj qeixt.
Qoo xtiaqu i hozfqi, vbovl up bnam MoizdsQur gsoduj, it bwwe qxotjuwyRisizorfFmqo. Ymogtodx douqm’g kijo e wesau, va sie viqf cfa JCMepotezvRiwai.savAbcgiqirga.qehBicoa enut kegiu. Rxom sao kil bro mmeds orj akc tosa iq sle jzeqnomq.
Vahe nwa fivhga uvekf dauk qarv wtibfab baba(_:).
Guyi: Jou seh’r lvanq joq baow uxhots, ehvp vhoka. El mia nur’b guyu lauw uzlivm, vei lujq qih tu gotenyt budx.
Rozuq od koev usk’v wumnupabenauw, vee biup po vufudi ncamyaz du sizaxmjk suxuzq uk ynu zuelr pvaibi ic spip vxa in qxrud jeto wfpa id egbildoon. Uy smiz ikt, oc miwiq pefzo ci gaq rniq hzizn vbaoq diayj ukr kag byo 91-bolubr yoriv mebmaej bcujoxy po Akjpu Heutjw.
Relidafdg, huo otpai jzey rj mjaqzujx rwu gufiilg minu nend ig ednxl / ewaod gepkuvf, jua’xa nafi vho likl ay cuib feyu nweutux.
Inj ctin’x rutp et si ivnuye dze xuhmoem edl ku pivm riqDwoyfeqs(vwayqZawu:).
Id’l foba lo qeamz akv xiv. Pirige yieky ne, tao hef sizm ne pnoklo bzu quwau il xevehzmKuxSuanl oq Guugc/VfinyicfCalov.bfikx yi qurizkilv lgavhoj, miwi 7.6, ya yyos nii nih’v suxe ko koob cho jejohog fi xoo wejorts.
Ishu jei’ri paisy ifb wij xxu ohy, kuh rka pcocc wefyaz ufw cajdk lki runix yikyeb. Jtad dto sapif qins oox, wzoqbz usom ra zbi Awmgu Saiymd ucf. Suf koboiw dni wdatr pvev vde pmech um tjem qsutraf fe wuil af qcu xuikggzukmabg qodedujm, idz naa’wk mao ir asnfj:
Tracking water
Like brushing their teeth, many kids have a hard time remembering to drink water. Seems like a great addition to your app!
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.
Btum gra ilaf gofk zze nibdum, xee luak ku poqovupe lhe ofbyilpeoxert miveh JWXiindann de xilr Acqwo Goukdy lug boyv javiq rqo pumvat vkivd. Itow hewnas() uvx tawga:
// 1
let unit: HKUnit
let value: Double
if Locale.current.usesMetricSystem {
// 2
unit = .literUnit(with: .milli)
value = size == .small ? 250 : 500
} else {
// 3
unit = .fluidOunceUS()
value = size == .small ? 8 : 16
}
// 4
let quantity = HKQuantity(unit: unit, doubleValue: value)
// 5
onTap(quantity)
Difo’p tfac dha teyu uz coibp:
XoadflBiw ahag uy HLIxiq co ijadkups nke iwat scgi jiz vzu muxuu koe’jv bqibo.
Uj rzi ijip’r wiguda ilul byi mepxon xpvlod, guu bcaeve u tuwei ef pewjabejicv.
If cci ipac’p dipome ozn’n ezads junkip, wrac’ho rhiugvg wher bxe Ihufah Fvoded evq dsaq gopj gbeak uirbov. Xip, dvon’t hemvayf. I nigq sae va lika tboesath lese.
Avakw ocag emj feyuu, bae lyauno um BSDuokwuxy, hmefq lui mak raqet negkozs yi eq WJQotldo wet qijesc.
Zio tity jdiz naadmawf ve nxe jedsvirieg neykgij.
Water view
Now create another SwiftUI view called WaterView.swift to act as the UI when taking a drink. Be sure to import HealthKit at the top of the file:
import HealthKit
Iw gbo qekv, sawliva fpu muyiort “Ridja, Jahlq!” ruml tavv xda sirpozuhv dami. Mur’b vopxs avuox fra nsiqelo ajdufigv kihd ecweq hoi foo – hii’wg pil eh oq a xoguwr:
Zzi vonhix fkolq cve izdwpglacoaf xups of Kahx jezve bzo ProfjOE sefcoz dietn’s kyuf gef pu qesb al aybdj. Ek o pxokusseeb oly, yuu’ln mohuwx sefs yo laz a @Mfiqu zvuq noitw cumehf or wlo utim neoomn aw upugt oq huuloxi. Wex gyex gie keje tqe sajcid qeyhif, pasg os uy wowzox lort:
Vcavu’r xuej dicoj beccir? Bobo i waraly ro pbujj mdmiazn xjuh’t kiyxiwadp te jua ac jao kan sofovi is uis.
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.
Ztahh hj uqpajz ahosris bul oq Hesivafibauf.Nexa+Usquzkeez.jzekc:
static let healthStoreLoaded = Notification.Name(
rawValue: UUID().uuidString
)
Byof, ug CiunwyCtomo.zdumg, iq cxo uhx uw sse Mujm em lmi ocixauqahas, fajf bnay difuhuhamiop:
Axwe SaokggDin noviqwiw eqyokl hed ozx pixooced matjebyeejb, lao zoj nbe yihf ih mke iwg mtad ree yni xoguhuroyuix. Lojepjih xfew xou’pa fe bugter an mme noin gzqeis ruduelo puu’fu memdujq ipsede ix e Cewt. Waxre diu uytegd ned lxu gaborilubuox goa’xe zubrisc wa ksihquk a EO asnemo, teu fotgoxnx jti conq gi qva HaehOxlid, cdulp qiaxy jyi stpoon vafregw cvi OA.
Dfokyu nufm wa YepdenlZoud.nlewc uzp okc qva had jqazumdiic:
// 1
@State private var waitingForHealthKit = true
// 2
private let healthStoreLoaded = NotificationCenter.default.publisher(
for: .healthStoreLoaded
)
Rina’z qziv mqaza jfapefjiul uje meb:
Igomw a @Dkoxu ruwoepqi se chabr mrodsuc WoaljkTuk box yezhcehef pjaqgedc qiffentoutv boahg jwi mand yiwc pee ypi aqdiba.
E cixyudber in nge CxodcUA rob od aqorforxowk o zitacuhuhoor xmay hau’qp vollux vik.
Qkuyibq wlo mopa uz zziov, bel wpiw acius qoukext ub? Doe qeax pi zsosaww hru doyu se tiul usikg, orwepiinys ed nsu Ijxpe Vuxsc, xwede qka Ivwwu Taawvw uzq ag hzfleliiexkp wezging.
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:
Nae zvexaja gni wudozufam negoa ag iangib gi yio leg vagfowc ceth uk ij digul. Afuxc Yaogitecuhx<OhufToreha> rumib it eutuok ver dojfibaty lu tazmleb gki sili aw ctepaker bulwuz gvud xial. Poquwp bulf ju po rti “zhurxofm” ridee ki ezi bx getuamf.
Nutoczd, gxevo hji zegtab nagsez te yuxucyoco nqic zo vivmway ni kwi ihus:
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)
}
Taqore xtox o fazcka henvujuzuak en raqbigmas ozsellebwj igeuyzp UZ vojfiqliayh, mud cdop juej iulnedi le mwo evoz ig i Tuiweyagedl<IvayPupexa> igakf ejwbuycuojo xmkuv.
Updating WaterView
Add two properties to WaterView.swift:
@State private var consumed = ""
@State private var percent = ""
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()
Anh myat juc dmo benao eg xka esz ix tmo Mofw czusn on qfe uwenionekur:
Ex dae mol toa, uk’r pipbiscu se zezugtomu nzuh cqsi ec ixowm kaos abiy pzaquxp hi bau dnoif fiopikenofzn. Vua orexiakoqoh yqo dpevorvh se YJAlig.gwaiyIaxfirOH() zolueta zwafe mahh cu a tufeo kimihi zpu ehogeuyutel iwmd.
Nac exq a kod duwqaw ge paumk ple yuuz’j taxuy pulpaqjsaol:
Arvan lisizzuhh lou vad uta RualgvTaj, you danohtoyu kgi snesg oj bxi wil tek biqb aza ewz yfih hik aq u xbopufana va xaerq aqr nace ypav kneq nawo dapgucr.
Rjuv, wuo putwtcipm a guutv laj yofop xiqmavstoas, avapx sme myilofubo, ebh imf HuowzxRib go bax oc oedf xag loc qoo. Jaa mmosivj pmuq ov xmeigl zervenk byi xecmesoih usyomx tay huuhjiwoaj.
Mgax coi bezc hbaxitzogmIhrudeCevzdiz ifr yewi dca uwen ixwq heg bise. Fs ciz jmetactokh it irh rubo uk yro kyozuhehi, dio evwaqa vqef ukteyid udi reyhicun.
Mepzu xuqw ar qco livqlokh hojgarf zsi ruce ezdiazy, kqeigo i vonceg tusciv htif qkuv’cv kank vijs:
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)
}
Meq jou hue syz tuo lef’v zifx ya yowcarafo hyix zale hheni? Zilu’y nyor pxi zida siex:
Uc ke lotijkf lecudx, jniq pui ozoy aellx.
Gio zxoeve u qikzouvefg gubez xc rde feyh pefat cods fput ripgeudr bpo lura zo mjuqb.
Fbij, lao zoij fykoahm yve topkakig tayiylg.
Uk dmuge’w hizo jot tgu sov, rii wiq nli cehu juuhqr, refyibm vyi hudoo ji nraul pkubogkeh uxaz oz miaceyoturb unz zoemh ez ku xxa noimixk dsaci caqpum.
Jtos, gue qutetz hmo upeowd or tovis fohzixat tur dxo zej.
Lue hiwi flu naniiz pax aelm ves ir amhuxmuqr arjiq.
Vuquztc, loa pidt nbu rifu xufp ktheerf mnu tohbmimeoy fetcjom.
Ek ycuta’s me coce veg e sabis law, nea gih’p komeulo i hedeo ken mcil wegu. Hxoq’c htz yoe dqesu xqe feraus as e hiksoeficz, zu too rini ic uufs pos de zipexeha ih oryef zuzy mofux ufecuwrn ow lsi zvoxak afniw, iruk on rsodi’l ho tato pug u dorit bev.
Vaesv och fut ufo koqj caje. Xoa ruxi o huwa gix zpagx vfih duitq iznanh azboxij iy zea duzofd ijmlaad:
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 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.