WWDC Quick Look 💓 By SwiftGGTeam
Build a research and care app, part 1: Setup onboarding

Build a research and care app, part 1: Setup onboarding

Watch original video

Highlight

ResearchKit and CareKit are used together to embed the ResearchKit survey process in the CareKit task card to achieve gated onboarding of “informed consent must be completed to access the Care plan”, while supporting unified requests for HealthKit, notification and device movement permissions.

Core Content

Two core requirements for healthcare research applications: collecting participant data (ResearchKit) and ongoing management of care plans (CareKit).In the past, these two frameworks were often used separately, and iOS 15 starts to show how to combine them.

The specific scenario is this: a physical therapy research app (called Recover) helps participants perform rehabilitation exercises after knee surgery.New users must complete informed consent before they can see specific rehabilitation tasks.This process is implemented in ResearchKit, but embedded in CareKit’s interface.

Key design points:

  • Onboarding tasks are displayed repeatedly every day until completed
  • Other tasks will not appear in the Care Feed until completed
  • Signatures and permission requests are integrated into one process

Persisting Onboarding tasks in CareKit Store

05:39

First create a CareKit task in AppDelegate and persist it to the Store:

// Create a daily repeating schedule
let dailySchedule = OCKSchedule.dailyAtTime(
    hour: 8, minutes: 0,
    start: Date(), end: nil,
    text: "Please complete this before 8 AM each day"
)

// Create the task
let onboardingTask = OCKTask(
    id: TaskID.onboarding,
    title: "Consent and onboarding",
    carePlanUUID: UUID(),
    schedule: dailySchedule
)
onboardingTask.instructions = "Please read and agree to the study terms"
onboardingTask.impactsAdherence = false  // Does not affect the completion ring

// Persist to the Store
store.addTaskAndWait(onboardingTask) { result in
    switch result {
    case .success:
        print("Onboarding task added successfully")
    case .failure(let error):
        print("Failed to add onboarding task: \(error)")
    }
}

Key points:

  • OCKSchedule.dailyAtTimeCreate daily recurring schedule
  • impactsAdherence = falseLet this task not count towards completion
  • The task ID must be unique and used for subsequent queries

Check Onboarding completion status

07:20

In CareFeedViewController, decide which tasks to display based on whether onboarding is completed:

func checkIfOnboardingIsComplete(completion: @escaping (Bool) -> Void) {
    let query = OCKOutcomeQuery()
    query.taskIDs = [TaskID.onboarding]

    store.fetchOutcomes(query: query) { outcomes, error in
        guard error == nil else {
            completion(false)
            return
        }

        // If an outcome is found, the task is complete
        completion(!(outcomes?.isEmpty ?? true))
    }
}

// Use in the prepare method
override func prepareListViewController(
    _ listViewController: OCKListViewController,
    for date: Date
) {
    checkIfOnboardingIsComplete { [weak self] isComplete in
        guard let self = self else { return }

        if isComplete {
            // Show all tasks
            self.appendAllTasks(to: listViewController, for: date)
        } else {
            // Show only onboarding tasks
            self.appendOnboardingTask(to: listViewController)
        }
    }
}

Key points:

  • OCKOutcomeQueryQuery task completion records
  • No outcome means the user has not finished yet
  • The query is asynchronous and needs to be handled in the completion handler

Create a ResearchKit Onboarding survey

09:31

Create a five-step onboarding process in Surveys.swift:

func createOnboardingSurvey() -> ORKTask {
    // Step 1: welcome page
    let welcomeStep = ORKInstructionStep(
        identifier: "onboarding.welcome"
    )
    welcomeStep.title = "Welcome to the Recover study"
    welcomeStep.detailText = "The goal of this study is to support post-surgical recovery"
    welcomeStep.image = UIImage(named: "welcome_header")

    // Step 2: informed explanation (icon-based bullet list)
    let consentStep = ORKInstructionStep(
        identifier: "onboarding.consent"
    )
    consentStep.title = "Informed Consent"
    consentStep.detailText = "Participating in this study requires you to understand the following information"

    // Use SF Symbols as bullets
    let healthDataItem = ORKBodyItem(
        text: "You will be asked to share health data",
        detailText: nil,
        image: UIImage(systemName: "heart.fill"),
        learnMoreItem: nil,
        bulletPoint: true
    )

    let tasksItem = ORKBodyItem(
        text: "You need to complete assigned recovery tasks",
        detailText: nil,
        image: UIImage(systemName: "checkmark.circle.fill"),
        learnMoreItem: nil,
        bulletPoint: true
    )

    let signatureItem = ORKBodyItem(
        text: "You need to provide a signature to indicate consent",
        detailText: nil,
        image: UIImage(systemName: "signature"),
        learnMoreItem: nil,
        bulletPoint: true
    )

    let privacyItem = ORKBodyItem(
        text: "Your data will be stored securely",
        detailText: "All data is encrypted",
        image: UIImage(systemName: "lock.fill"),
        learnMoreItem: nil,
        bulletPoint: true
    )

    consentStep.bodyItems = [
        healthDataItem,
        tasksItem,
        signatureItem,
        privacyItem
    ]

    // Step 3: signature (HTML informed consent form)
    let signatureStep = ORKWebViewStep(
        identifier: "onboarding.signature",
        html: getConsentDocumentHTML()
    )
    signatureStep.showSignatureAfterContent = true

    // Step 4: permission request
    let healthTypesToWrite: Set<HKSampleType> = [
        HKObjectType.quantityType(forIdentifier: .stepCount)!
    ]
    let healthTypesToRead: Set<HKSampleType> = [
        HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!
    ]

    let healthPermission = ORKHealthKitPermissionType(
        typesToWrite: healthTypesToWrite,
        typesToRead: healthTypesToRead
    )

    let notificationPermission = ORKNotificationPermissionType()
    let motionPermission = ORKMotionActivityPermissionType()

    let permissionsStep = ORKRequestPermissionsStep(
        identifier: "onboarding.permissions"
    )
    permissionsStep.permissionTypes = [
        healthPermission,
        notificationPermission,
        motionPermission
    ]

    // Step 5: completion page
    let completionStep = ORKCompletionStep(
        identifier: "onboarding.completion"
    )
    completionStep.title = "Thank you for participating"
    completionStep.detailText = "You can now start using the Recover app"

    // Assemble into an ordered task
    return ORKOrderedTask(
        identifier: "onboarding",
        steps: [
            welcomeStep,
            consentStep,
            signatureStep,
            permissionsStep,
            completionStep
        ]
    )
}

Key points:

  • ORKInstructionStepUsed to display descriptive text
  • ORKBodyItemSupport SF Symbol as bullet symbol
  • ORKWebViewStepofshowSignatureAfterContent = trueShow signature box
  • New in iOS 15ORKNotificationPermissionTypeandORKMotionActivityPermissionType

Display ResearchKit surveys in CareKit

11:59

Use the newly introducedOCKSurveyTaskViewController

func appendOnboardingTask(
    to listViewController: OCKListViewController
) {
    // Create the event query
    let eventQuery = OCKEventQuery(
        for: Date(),
        taskIDs: [TaskID.onboarding]
    )

    // Create the survey task view controller
    let surveyVC = OCKSurveyTaskViewController(
        taskID: TaskID.onboarding,
        eventQuery: eventQuery,
        storeManager: storeManager,
        survey: createOnboardingSurvey(),
        resultHandler: { result in
            // Convert ResearchKit results into CareKit outcome values
            let dateAnswer = result.results?.first(where: {
                $0.identifier == "onboarding.completion"
            }) as? ORKDateQuestionResult

            let outcomeValue = OCKOutcomeValue(
                dateAnswer?.dateAnswer ?? Date()
            )

            return [outcomeValue]
        }
    )

    surveyVC.delegate = self

    // Create the task card and add it to the list
    let card = OCKSimpleTaskView(
        taskID: TaskID.onboarding,
        eventQuery: eventQuery,
        owner: self
    )
    card.controller = surveyVC

    listViewController.appendView(card)
}

Key points:

  • OCKSurveyTaskViewControllerDesigned specifically for displaying ResearchKit surveys in CareKit
  • resultHandlerThe closure is responsible for converting ResearchKit results into CareKit outcomes -Set delegate to listen for completion event

Process Onboarding completion and refresh the interface

12:49

Implement the delegate method to refresh the feed when the user completes onboarding:

extension CareFeedViewController: OCKSurveyTaskViewControllerDelegate {
    func surveyTaskViewController(
        _ viewController: OCKSurveyTaskViewController,
        didFinish task: ORKTask,
        with result: TaskResult
    ) {
        // Check whether it completed successfully (the user did not cancel)
        guard case .success = result else {
            return
        }

        // Reload the feed and show other tasks
        reload()
    }

    func surveyTaskViewController(
        _ viewController: OCKSurveyTaskViewController,
        didCancel task: ORKTask
    ) {
        // The user canceled; no special handling is needed
    }
}

Key points:

  • reload()Refresh the entire calendar page
  • After completion, the onboarding card no longer displays and other tasks start to appear.
  • Cancellation by the user is not considered completed, and onboarding will still be displayed next time it is opened.

Detailed Content

The HTML document required for the signing step should contain the complete legal text:

func getConsentDocumentHTML() -> String {
    return """
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <style>
            body { font-family: -apple-system; padding: 20px; }
            h1 { color: #007aff; }
            .section { margin: 20px 0; }
        </style>
    </head>
    <body>
        <h1>Recover Study Informed Consent Form</h1>

        <div class="section">
            <h2>1. Study Purpose</h2>
            <p>This study evaluates the effect of physical therapy on recovery after knee surgery.</p>
        </div>

        <div class="section">
            <h2>2. Data Collection</h2>
            <p>We will collect your health data, including daily step count, active energy, and more.</p>
        </div>

        <div class="section">
            <h2>3. Privacy Protection</h2>
            <p>All data is encrypted and accessible only to the study team.</p>
        </div>

        <div class="section">
            <h2>4. Voluntary Participation</h2>
            <p>You may leave the study at any time without affecting any medical care.</p>
        </div>
    </body>
    </html>
    """
}

Key points:

  • Use HTML5 standard format
  • Can inline CSS styles
  • The signature box will automatically appear below the content

HealthKit permission type description

When requesting HealthKit permissions, you need to explicitly specify the type you want to read and write:

CategoryCommon typesUsage
Sports datastepCountdistanceWalkingRunningActivity statistics
Physical fitness indexactiveEnergyBurnedbasalEnergyBurnedCalorie consumption
Health IndicatorsheartRatebloodPressurePhysiological indicators
Sleep datasleepAnalysisSleep quality

Key points:

  • Request only required data types
  • Clearly state the purpose of each data in the informed consent form
  • Users can revoke permissions at any time in settings

Core Takeaways

  1. gated onboarding for medical research
  • What to do: User must complete informed consent to access the main features of the App
  • Why it’s worth doing: Medical research must obtain explicit consent from participants, otherwise there are legal risks.Gated onboarding ensures each user has read the consent form
  • How ​​to start: Use ResearchKit to create a consent process, and query the outcome status in CareKit to control content display
  1. Use SF Symbol to optimize the readability of information notes
  • What to do: InORKInstructionStepofbodyItemsUse SF Symbol instead of normal bullets in
  • Why it’s worth it: Medical/legal texts are often difficult to understand, visual elements can aid understanding and reduce user abandonment rates
  • How ​​to get started: CreateORKBodyItemwhen passed inUIImage(systemName:)
  1. Integrated HealthKit, notifications and motion permission requests
  • What to do: Request all required permissions in one go during the onboarding process
  • Why it’s worth doing: Dispersed permission requests interrupt the user experience, unified requests make more sense in the context of informed consent
  • How ​​to start: UseORKRequestPermissionsStepcombinationORKHealthKitPermissionTypeORKNotificationPermissionTypeORKMotionActivityPermissionType
  1. Make a verifiable electronic signature
  • What to do: UseORKWebViewStepCollect user signatures and save them in CareKit outcome
  • Why it’s worth it: Electronic signatures are legally binding and more formal than a simple “I agree” button
  • How ​​to start:showSignatureAfterContent = true+ HTML consent document
  1. Guide a personalized rehabilitation plan
  • What to do: Ask the user about their recovery goals at the end of onboarding, and then customize CareKit tasks based on the goals.
  • Why it’s worth it: Personalized care plans can significantly improve user engagement and recovery outcomes
  • How ​​to start: Add a completion step beforeORKFormStepCollect user preferences, store them in outcome and use them for task scheduling

Comments

GitHub Issues · utterances