Using ResearchKit on iOS for prototype study tasks and result handling

This article focuses on one practical flow: add ResearchKit to an existing iOS app, present built-in study tasks through ORKTaskViewController, and read back typed results for cognitive, visual, and hearing-oriented workflows.

ResearchKit spatial span memory task running on iPhone

This article is most useful as a catalog of what ResearchKit already gives you before you build any custom study UI yourself.

Instead of inventing every interaction from scratch, this article walks through the built-in task factories already available in ORKOrderedTask. The value is not only the screens themselves. It is the pattern: create a task, present it with ORKTaskViewController, then inspect the result object that matches that task type.

Research only This article is explicit that these examples are for research and framework exploration, not for clinical diagnosis. Real medical examination workflows belong to qualified professionals, not demo code.

The integration steps are simple: bring the framework into your project, then embed the built product like any other dependency.

This article starts by cloning the open source framework from git@github.com:ResearchKit/ResearchKit.git. After that, it uses the older but still understandable manual Xcode route: drag the ResearchKit.xcodeproj file into your app project, then add the built product under your target's Frameworks, Libraries, and Embedded Content section with Embed & Sign.

Dragging the ResearchKit Xcode project into an existing iOS app project
Add the ResearchKit project to the app workspace first.
Adding ResearchKit under Frameworks Libraries and Embedded Content in Xcode
Then attach the built framework to the target with Embed & Sign.

Every task in the article follows the same lifecycle: create the task, present the controller, dismiss it, then cast the result back to the task-specific type you need.

The reusable part of the article is the controller pattern, not any one medical test. Once the framework is linked, you import ResearchKit, build one task, and present an ORKTaskViewController. Results come back through ORKTaskViewControllerDelegate, where result.identifier and stepResult(forStepIdentifier:) help you route the data to the correct parser.

import ResearchKit

let taskViewController = ORKTaskViewController(task: task, taskRun: nil)
taskViewController.outputDirectory = FileManager.default.temporaryDirectory
taskViewController.delegate = self
present(taskViewController, animated: true)
extension TestingViewController: ORKTaskViewControllerDelegate {
    func taskViewController(
        _ taskViewController: ORKTaskViewController,
        didFinishWith reason: ORKTaskViewControllerFinishReason,
        error: Error?
    ) {
        taskViewController.dismiss(animated: true)
    }

    func taskViewController(_ taskViewController: ORKTaskViewController, didChange result: ORKTaskResult) {
        if result.identifier == "spatialSpanMemory",
           let memoryResult = result
                .stepResult(forStepIdentifier: "cognitive.memory.spatialspan")?
                .results?
                .first as? ORKSpatialSpanMemoryResult {
            let score = memoryResult.score
            let failures = memoryResult.numberOfFailures
            let totalGames = memoryResult.numberOfGames
        }
    }
}
Pattern The task factory chooses the UI, but the typed result object is what makes the workflow usable in code. That is the main thread running through the whole post.

Most of the article is a tour of built-in ResearchKit tasks across memory, motor speed, puzzle solving, visual testing, and serial arithmetic.

This article groups a wide range of ready-made tasks in one place. That is helpful because the API naming is often more discoverable after you see the screen once. In this rewrite, the gallery below keeps the visual map while calling out the factory method each task came from.

Spatial span memory task from ResearchKit
Spatial memory via spatialSpanMemoryTask(...).
Two finger tapping interval task from ResearchKit
Tapping speed via twoFingerTappingIntervalTask(...).
Tower of Hanoi task from ResearchKit
Problem solving with towerOfHanoiTask(...).
Trail making task from ResearchKit
Trail making with trailmakingTask(...).
Landolt C contrast sensitivity task from ResearchKit
Contrast sensitivity via landoltCContrastSensitivityTask(...).
Landolt C visual acuity task from ResearchKit
Visual acuity via landoltCVisualAcuityTask(...).
Paced serial addition task from ResearchKit
Serial addition with psatTask(...).
Tone audiometry task from ResearchKit
Tone hearing checks via toneAudiometryTask(...).

The post does not treat these as one giant generic result. It handles each task through its matching result type, such as ORKSpatialSpanMemoryResult, ORKPSATResult, or ORKToneAudiometryResult.

The hearing sections are the most code-heavy part because the result data is more nested than the simpler cognitive tasks.

For the regular tone audiometry task, this article checks whether the selected channel matched the played channel and records the missed frequencies. For the dBHL task, it goes one level deeper and iterates through each sample's units to count how many loudness levels the participant acknowledged at each frequency.

if let audioResult = result
    .stepResult(forStepIdentifier: "tone.audiometry")?
    .results?
    .first as? ORKToneAudiometryResult,
   let samples = audioResult.samples {
    let totalTestCount = samples.count
    let matchedCount = samples.filter { $0.channel == $0.channelSelected }.count
    let missedFrequencies = samples
        .filter { $0.channel != $0.channelSelected }
        .map { String(format: "%.2f", $0.frequency) }
}

The dBHL variant uses dBHLToneAudiometryTask(...) and returns per-frequency samples whose nested units carry individual dBHLValue measurements plus the tap timestamp that tells you whether the tone was acknowledged. That makes the hearing flow the clearest example in the article of why you usually need custom post-processing after the framework hands the raw result back.

Why this matters The cognitive tasks mostly give you a compact score or count. The audio tasks behave more like structured measurement data, so you should plan the parsing layer before you treat the UI task as done.

The article still holds up because it treats ResearchKit less like a single framework import and more like a toolbox of typed study workflows.

The important takeaway is not any one memory or hearing task. It is the repeatable pattern: use the built-in task factories when they match your prototype, present them through ORKTaskViewController, then inspect the typed result objects instead of trying to force every workflow through one generic parser. That is what makes this article a useful reference even years later.