2020 Health & Fitness
WWDC20 · 36 min · Health & Fitness
What’s new in CareKit
Build feature-rich research and care apps with CareKit: Learn about the latest advancements to our health framework, including new views for its modular architecture, improvements to the data store, and tighter integration with other frameworks on iOS. And discover how the open-source community continues to leverage CareKit to allow developers to push the boundaries of digital health — all while preserving privacy.
Watch at developer.apple.com ↗Code shown on screen · 31 snippets
Simple Task View
import CareKitUI
import SwiftUI
struct MySimpleTaskView: View {
var body: some View {
SimpleTaskView(
title: Text("Stretches"),
detail: Text("15 minutes"),
isComplete: false)
}
} Simple Task View - View Modifiers
import CareKitUI
import SwiftUI
struct MySimpleTaskView: View {
var body: some View {
SimpleTaskView(
title: Text("Stretches").fontWeight(.thin),
detail: Text("15 minutes"),
isComplete: false)
}
} Simple Task View - Custom Header
struct MySimpleTaskView: View {
var body: some View {
SimpleTaskView(isComplete: false) {
HStack {
RoundedRectangle(cornerRadius: 5)
.fill(Color.accentColor)
.frame(width: 5)
HeaderView(
title: Text("Stretches"),
detail: Text("15 minutes"))
}
.padding()
}
}
} Simple Task View - Appending Views
import CareKitUI
import SwiftUI
struct MyComposedSimpleTaskView: View {
var body: some View {
CardView {
VStack(alignment: .leading) {
MySimpleTaskView()
Divider()
Text("...")
.font(.caption)
.foregroundColor(.secondary)
}.padding()
}
}
} Labeled Value Task View
import CareKitUI
import SwiftUI
struct MyLabeledValueTaskView: View {
var body: some View {
LabeledValueTaskView(
title: Text("Heart Rate"),
detail: Text("Most recent measurement")
state: .complete(
Text("62"),
Text("BPM")
))
}
} Numeric Progress Task View
import CareKitUI
import SwiftUI
struct MyNumericProgressView: View {
var body: some View {
NumericProgressTaskView(
title: Text("Exercise Minutes"),
detail: Text("Anytime"),
instructions: Text("..."),
progress: Text("22"),
goal: Text("30"),
isComplete: false)
}
} Featured Content View
import CareKitUI
import UIKit
let featureView = OCKFeaturedContentView()
featureView.imageView.image = UIImage(named: "groceries")
featureView.label.text = "Easy & Healthy Recipes" Detail View
import CareKitUI
import UIKit
let styledHTML = OCKDetailView.StyledHTML(
html: html,
css: css)
let detailView = OCKDetailView(
html: styledHTML,
showsCloseButton: true)
detailView.imageView.image = UIImage(named: "groceries") Link View
import CareKitUI
import SwiftUI
struct MyLinkView: View {
var body: some View {
LinkView(
title: Text("Physical Therapist Appointment"),
instructions: Text("..."),
links: [
// ...
.website(
"https://www.apple.com",
title: "Website")
// ...
])
}
} Synchronized Task View 1
// Synchronized Task View
import CareKit
import CareKitUI
import SwiftUI
struct MySynchronizedTaskView: View {
let storeManager: OCKSynchronizedStoreManager
var body: some View {
CareKit.SimpleTaskView(
taskID: "stretch",
eventQuery: OCKEventQuery(for: Date()),
storeManager: storeManager)
}
} Synchronized Task View 2
private var isShowingSurvey = false
var body: some View {
CareKit.SimpleTaskView(
taskID: "researchKitSurveyTask",
eventQuery: OCKEventQuery(for: Date()),
storeManager: storeManager) { controller in
CareKitUI.SimpleTaskView(
title: Text(controller.viewModel?.title ?? ""),
detail: controller.viewModel?.detail.map(Text.init),
isComplete: controller.viewModel?.isComplete ?? false) {
isShowingSurvey = true
}
}
.popover(isPresented: $isShowingSurvey) {
ResearchKitSurvey()
}
} Setting up the store
// Setting up the Store
import CareKit
import CareKitStore
let coreDataStore = OCKStore(name: "core-data-store")
let healthKitPassthroughStore = OCKHealthKitPassthroughStore(name: "hk-passthrough—store")
let coordinator = OCKStoreCoordinator()
coordinator.attach(store: coreDataStore)
coordinator.attach(eventStore: healthKitPassthroughStore)
let storeManager = OCKSynchronizedStoreManager(wrapping: coordinator) Adding HealthKit linked tasks to the store
// Adding HealthKit Linked Tasks to the Store
let schedule = OCKSchedule.dailyAtTime(
hour: 8,
minutes: 0,
start: Date(),
end: nil,
text: nil,
duration: .allDay,
targetValues: [OCKOutcomeValue(30.0, units: "Minutes")])
let link = OCKHealthKitLinkage(
quantityIdentifier: .appleExerciseTime,
quantityType: .cumulative,
unit: .minute())
let steps = OCKHealthKitTask(
id: "exerciseMinutes",
title: "Exercise Minutes",
carePlanUUID: nil,
schedule: schedule,
healthKitLinkage: link)
storeManager.store.addAnyTask(steps) Encoding and decoding FHIR data
// Encoding and Decoding FHIR Data
import CareKitStore
import CareKitFHIR
let coder = OCKR4PatientCoder()
// CareKit entity to FHIR data
let patient = OCKPatient(...)
let json = try! coder.encode(patient)
// FHIR data to CareKit entity
let data: Data = //...
let resourceData = OCKFHIRResourceData<R4, JSON>(data: data)
let patient: OCKPatient = try! coder.decode(resourceData) Customizing the data mapping 1
// Customizing the Data Mapping
// Encoding process
coder.setFHIRName = { name, fhirPatient in // ModelsR4.Patient
let humanName = HumanName()
humanName.family = name.familyName.map { FHIRPrimitive(FHIRString($0)) }
humanName.given = name.givenName.map { [FHIRPrimitive(FHIRString($0))] }
fhirPatient.name = [humanName]
} Customizing the data mapping 2
// Customizing the Data Mapping
// Encoding process
coder.setFHIRName = { name, fhirPatient in // ModelsR4.Patient
let humanName = HumanName()
humanName.family = name.familyName.map { FHIRPrimitive(FHIRString($0)) }
humanName.given = name.givenName.map { [FHIRPrimitive(FHIRString($0))] }
fhirPatient.name = [humanName]
} Creating the remote
private lazy var remote = OCKWatchConnectivityPeer() Setting up the remote store
private lazy var store = OCKStore(name: "sample-store", remote: remote) import WatchConnectivity
import WatchConnectivity Setting up the Watch Connectivity Session
WCSession.default.delegate = sessionDelegate
WCSession.default.activate() Stub out session delegate class
class SessionDelegate: NSObject, WCSessionDelegate {
let remote: OCKWatchConnectivityPeer
let store: OCKStore
init(remote: OCKWatchConnectivityPeer, store: OCKStore) {
self.remote = remote
self.store = store
}
func session(
_ session: WCSession,
activationDidCompleteWith activationState: WCSessionActivationState,
error: Error?) {
print("New session state: \(activationState)")
}
func session(
_ session: WCSession,
didReceiveMessage message: [String: Any],
replyHandler: @escaping ([String: Any]) -> Void) {
print("Received message from peer!")
}
} Kick off first synchronization
if activationState == .activated {
store.synchronize { error in
print(error?.localizedDescription ?? "Successful sync!")
}
} Forwarding replies on CareKit's behalf
remote.reply(to: message, store: store) { reply in
print("Sending reply to peer!")
replyHandler(reply)
} Creating the delegate
private lazy var sessionDelegate = SessionDelegate(remote: remote, store: store) Setting up the synchronized store manager
private(set) lazy var storeManager = OCKSynchronizedStoreManager(wrapping: store) Defining a new environment key
private struct StoreManagerKey: EnvironmentKey {
static var defaultValue: OCKSynchronizedStoreManager {
let extensionDelegate = WKExtension.shared().delegate as! ExtensionDelegate
return extensionDelegate.storeManager
}
} Defining a store manager environment value
extension EnvironmentValues {
var storeManager: OCKSynchronizedStoreManager {
get {
self[StoreManagerKey.self]
}
set {
self[StoreManagerKey.self] = newValue
}
}
} Adding an environment variable to the view
(\.storeManager) private var storeManager Setting up the scroll view
ScrollView {
}.accentColor(Color(#colorLiteral(red: 0.9960784314, green: 0.3725490196, blue: 0.368627451, alpha: 1))) Displaying the stretch task card
InstructionsTaskView(
taskID: "stretch",
eventQuery: OCKEventQuery(for: Date()),
storeManager: storeManager) Displaying the muscle cramps task card
SimpleTaskView(
taskID: "cramps",
eventQuery: OCKEventQuery(for: Date()),
storeManager: storeManager) { controller in
.init(title: Text(controller.viewModel?.title ?? ""),
detail: nil,
isComplete: controller.viewModel?.isComplete ?? false,
action: controller.viewModel?.action ?? {})
} Resources
Related sessions
-
31 min -
21 min -
32 min -
8 min