Note: I tried posting on the apple developer forums 2 days ago and didn't receive any responses as well
I've been literally stuck on this portion of my project because I want to mirror the way apple has their care view setup for displaying tasks to users. I for some reason, can't subclass the 'OCKSurveyTaskViewController' as the error I'm getting is:
"Cannot find type 'OCKSurveyTaskViewController' in scope"
I've reinstalled CareKit through SPM 2 or three times and I can't figure out why I can't subclass it even when I see the exact OCKSurveyTaskViewController.swift file and it listed in the code as an open class in the CareKit/iOS/Task/ViewController directory from Xcode.
Could anyone please give me some guidance or perhaps another way to display the ORKTasks I have established for users on a daily basis in another fashion? I prefer the method apple Is using in their wwdc21 CareKit code along but alas I've been stuck on this for too long and this is needed to get to my next step.
Here's my files code:
import UIKit
import CareKit
import CareKitUI
import CareKitStore
import ResearchKit
import os.log
class StudyTaskFeedViewController: OCKDailyTasksPageViewController, OCKSurveyTaskViewController {}
Here's the code to the OCKSurveyTaskViewController.swift file:
#if !os(watchOS) && canImport(ResearchKit)
import CareKitStore
import CareKitUI
import ResearchKit
import UIKit
// MARK: OCKSurveyTaskViewControllerDelegate
public protocol OCKSurveyTaskViewControllerDelegate: AnyObject {
func surveyTask(
viewController: OCKSurveyTaskViewController,
for task: OCKAnyTask,
didFinish result: Result<ORKTaskViewControllerFinishReason, Error>)
func surveyTask(
viewController: OCKSurveyTaskViewController,
shouldAllowDeletingOutcomeForEvent event: OCKAnyEvent) -> Bool
}
public extension OCKSurveyTaskViewControllerDelegate {
func surveyTask(
viewController: OCKSurveyTaskViewController,
for task: OCKAnyTask,
didFinish result: Result<ORKTaskViewControllerFinishReason, Error>) {
// No-op
}
func surveyTask(
viewController: OCKSurveyTaskViewController,
shouldAllowDeletingOutcomeForEvent event: OCKAnyEvent) -> Bool {
return true
}
}
open class OCKSurveyTaskViewController: OCKTaskViewController<OCKTaskController, OCKSurveyTaskViewSynchronizer>, ORKTaskViewControllerDelegate {
private let extractOutcome: (ORKTaskResult) -> [OCKOutcomeValue]?
public let survey: ORKTask
public weak var surveyDelegate: OCKSurveyTaskViewControllerDelegate?
public convenience init(
task: OCKAnyTask,
eventQuery: OCKEventQuery,
storeManager: OCKSynchronizedStoreManager,
survey: ORKTask,
viewSynchronizer: OCKSurveyTaskViewSynchronizer = OCKSurveyTaskViewSynchronizer(),
extractOutcome: @escaping (ORKTaskResult) -> [OCKOutcomeValue]?) {
self.init(
taskID: task.id,
eventQuery: eventQuery,
storeManager: storeManager,
survey: survey,
viewSynchronizer: viewSynchronizer,
extractOutcome: extractOutcome
)
}
public init(
taskID: String,
eventQuery: OCKEventQuery,
storeManager: OCKSynchronizedStoreManager,
survey: ORKTask,
viewSynchronizer: OCKSurveyTaskViewSynchronizer = OCKSurveyTaskViewSynchronizer(),
extractOutcome: @escaping (ORKTaskResult) -> [OCKOutcomeValue]?) {
self.survey = survey
self.extractOutcome = extractOutcome
super.init(
viewSynchronizer: viewSynchronizer,
taskID: taskID,
eventQuery: eventQuery,
storeManager: storeManager
)
}
override open func taskView(
_ taskView: UIView & OCKTaskDisplayable,
didCompleteEvent isComplete: Bool,
at indexPath: IndexPath,
sender: Any?) {
guard isComplete else {
if let event = controller.eventFor(indexPath: indexPath),
let delegate = surveyDelegate,
delegate.surveyTask(
viewController: self,
shouldAllowDeletingOutcomeForEvent: event) == false {
return
}
let cancelAction = UIAlertAction(
title: "Cancel",
style: .cancel,
handler: nil
)
let confirmAction = UIAlertAction(
title: "Delete", style: .destructive) { _ in
super.taskView(
taskView,
didCompleteEvent: isComplete,
at: indexPath,
sender: sender
)
}
let warningAlert = UIAlertController(
title: "Delete",
message: "Are you sure you want to delete your response?",
preferredStyle: .actionSheet
)
warningAlert.addAction(cancelAction)
warningAlert.addAction(confirmAction)
present(warningAlert, animated: true, completion: nil)
return
}
let surveyViewController = ORKTaskViewController(
task: survey,
taskRun: nil
)
let directory = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
).last!.appendingPathComponent("ResearchKit", isDirectory: true)
surveyViewController.outputDirectory = directory
surveyViewController.delegate = self
present(surveyViewController, animated: true, completion: nil)
}
// MARK: ORKTaskViewControllerDelegate
open func taskViewController(
_ taskViewController: ORKTaskViewController,
didFinishWith reason: ORKTaskViewControllerFinishReason,
error: Error?) {
taskViewController.dismiss(animated: true, completion: nil)
guard let task = controller.taskEvents.first?.first?.task else {
assertionFailure("Task controller is missing its task")
return
}
if let error = error {
surveyDelegate?.surveyTask(
viewController: self,
for: task,
didFinish: .failure(error)
)
return
}
guard reason == .completed else {
return
}
let indexPath = IndexPath(item: 0, section: 0)
guard let event = controller.eventFor(indexPath: indexPath) else {
return
}
guard let values = extractOutcome(taskViewController.result) else {
return
}
let outcome = OCKOutcome(
taskUUID: event.task.uuid,
taskOccurrenceIndex: event.scheduleEvent.occurrence,
values: values
)
controller.storeManager.store.addAnyOutcome(
outcome,
callbackQueue: .main) { result in
if case let .failure(error) = result {
self.surveyDelegate?.surveyTask(
viewController: self,
for: task,
didFinish: .failure(error)
)
}
self.surveyDelegate?.surveyTask(
viewController: self,
for: task,
didFinish: .success(reason)
)
}
}
}
#endif
I encountered the same problem when I try to reproduce the demostration code form the "Recover" app in my own app. After I compared my project settings with the "Recover", I noticed that I missed to add the capability of "HealthKit" in my target app's Sign&Capabilities. After I added this capability, it worked.