【问题标题】:Why can't I subclass 'OCKSurveyTaskViewController' from CareKit?为什么我不能从 CareKit 子类化“OCKSurveyTaskViewController”?
【发布时间】:2021-08-10 13:10:30
【问题描述】:

注意:我在 2 天前尝试在苹果开发者论坛上发帖,也没有收到任何回复

我一直坚持我的项目的这一部分,因为我想反映苹果为向用户显示任务而设置护理视图的方式。由于某种原因,我无法将“OCKSurveyTaskViewController”子类化,因为我得到的错误是:

“在范围内找不到类型 'OCKSurveyTaskViewController'”

我已经通过 SPM 2 或 3 次重新安装了 CareKit,即使我看到确切的 OCKSurveyTaskViewController.swift 文件并且它在代码中作为开放类在来自 Xcode 的 CareKit/iOS/Task/ViewController 目录。

谁能给我一些指导或另一种方式来显示我每天为用户建立的 ORKTasks 以另一种方式?我更喜欢苹果在他们的 wwdc21 CareKit 代码中使用的方法,但可惜我已经坚持了太久,这是我下一步所必需的。

这是我的文件代码:

import UIKit
import CareKit
import CareKitUI
import CareKitStore
import ResearchKit
import os.log

class StudyTaskFeedViewController: OCKDailyTasksPageViewController, OCKSurveyTaskViewController {}

这是 OCKSurveyTaskViewController.swift 文件的代码:

#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

【问题讨论】:

  • 在您的 StudyTaskFeedViewController 类中,您实际上是在尝试进行多重继承(来自 OCKDailyTasksPageViewController 和 OCKSurveyTaskViewController 类)。这在 Swift 中是不可能的。如果你在其他 Swift 代码中看到过类似的语法,那是因为一个类可以符合多个协议(除了可选地从超类继承)。
  • 啊,我明白了,多继承对类不起作用。我现在看到我正在尝试继承 OCKSurveyTaskViewController 的委托,但仍然在输入 Xcode 时,上述文件中的代码都没有在代码完成中列出。有关更多上下文,当我尝试遵循 WWDC21 中的会话代码时,文件应该是这样的:github.com/carekit-apple/WWDC21-RecoverApp/blob/main/…
  • 根据您的目标和环境,您甚至可能无法通过第一行的#if 语句。如果确实发生了这种情况,那么基本上你的整个第二个文件都不会被编译。
  • 明白!所以我可以假设这个#if语句是说“如果目标不是WatchOS并且还可以导入ResearchKit框架,那么这个文件中的所有对象都可以访问/用于继承”?另外,感谢您花时间回答我的问题;几个月来,我一直在自学如何编码,现在发现需要为我的实验室领导的项目使用框架。
  • 您正确解释了#if 编译器指令的条件。但是,它告诉编译器它应该在编译过程中包含哪些代码部分以及排除哪些代码部分。如果您有要在多个平台上编译的代码,但这些平台并不都支持相同的功能,编译器将不会编译该代码,您会得到错误。使用编译器指令,您可以有条件地包含/排除不同平台的代码、调试等。如果在您的情况下不满足条件,则代码将完全从编译中排除。

标签: swift xcode carekit


【解决方案1】:

当我尝试在我自己的应用程序中从“恢复”应用程序中重现演示代码时,我遇到了同样的问题。在将我的项目设置与“恢复”进行比较后,我注意到我错过了在目标应用程序的 Sign&Capabilities 中添加“HealthKit”的功能。在我添加此功能后,它就起作用了。

【讨论】:

    猜你喜欢
    • 2013-03-13
    • 1970-01-01
    • 2017-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多