翻譯自蘋果官方文檔,譯者:@星夜暮晨,@有情況_R
調查任務 (Survey Task) 是表示一系列問題的步驟對象 (ORKStep) 集合,例如說『您正在服用何種藥物?』,或者『您昨晚睡了幾個小時?』之類的問題。您可以收集單個步驟的調查結果,也可以收集整個任務的調查結果。
要創建一個用以展示調查的任務,步驟如下:
創建一個或多個步驟
創建一個任務
收集調查結果
1. 創建步驟
調查模塊不僅提供了只包含一個問題的步驟 (ORKQuestionStep) ,還提供了可以包含多個項目的表單步驟 (ORKFormStep)。您同樣可以使用指示步驟 (ORKInstructionStep) 來介紹調查的相關信息,或者為用戶提供明確的指示。
每一個步驟都擁有自己的步驟視圖控制器,這個控制器定義了用以展示這種類型步驟的用戶界面。當某個任務視圖控制器需要展示一個步驟的時候,它會將合適的步驟視圖控制器進行實例化,然後將其展示出來。如果有必要的話,您可以通過實現任務視圖控制器委托方法 (參見 ORKTaskViewControllerDelegate),來自定義每一個步驟視圖控制器的細節信息,例如按鈕標題以及風格。
指示步驟
指示步驟用以說明某個任務的目的所在,並且為用戶提供相關的指示。ORKInstructionStep 對象包含了一個標識符、標題、文本、詳情文本,以及一個圖片。由於指示步驟並不會收集任何數據,不過它仍然會生成一個空的 ORKStepResult 來記錄指示信息顯示在屏幕上的時間。
創建一個如上面代碼中所示的步驟,將其置入到任務當中,然後在任務視圖控制器中展示出來,這樣就會產生類似於這樣的東西:
指示步驟示例
問題步驟
問題步驟 (ORKQuestionStep) 代表了一個簡單的問題,它由一個簡短的 title 和一個更長、更詳細的 text組成。可以通過設置回答格式來配置用戶可以輸入類型的數據。您同樣可以為用戶提供一個選項,讓他們能夠通過步驟的 optional 屬性來跳過問題。
對於數字和文本格式的回答,問題步驟的 placeholder 屬性指定了一個提示,其會在輸入框中對預期值進行描述。
問題步驟會生成一個步驟結果,就和指示步驟產生的結果一樣,用來表明用戶面對屏幕上的這個問題面對了多長時間。它同樣也包含了一個 ORKQuestionResult 子類,用來報告用戶的回答。
下列代碼配置了一個簡單的數字問題步驟:
將這個問題步驟添加到任務當中,然後展示這個任務,這會生成一個這樣的屏幕:
問題步驟示例
表單步驟
當用戶需要同時回答多個相互聯系的問題時,最好使用表單步驟 (ORKFormStep),這樣可以將這些步驟全部展現在一個頁面當中。表單步驟不僅支持將所有的問題步驟全部應用上相同的回答格式,而且還能夠包含多個項目 (ORKFormItem),每個項目都可以包含自己的回答格式。
表單可以通過與只包含一個標題的一個額外 “虛擬”表單項目組合構成多個章節。參見 ORKFormItem 參考文檔獲取更多信息。
表單步驟的結果和問題步驟的結果類似,只是每個表單項目當中都會包含有問題結果。使用表單項目的標識符 (the identifier 屬性) 可以獲取相應的表單項目所對應的結果。
例如,下面這段代碼展示了如何創建一個請求某些基礎問題的一個表單,使用從 iOS 中的 HealthKit 提取的默認值來填充這些數據輸入框:
OBJECTIVE C
ORKFormStep*step= [[ORKFormStep alloc]initWithIdentifier:kFormIdentifier title:@"表單" text:@"單頁展示含有多個輸入框的表單組"]; NSMutableArray*items=[NSMutableArray new]; ORKAnswerFormat*genderFormat= [ORKHealthKitCharacteristicTypeAnswerFormat answerFormatWithCharacteristicType: [HKCharacteristicType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierBiologicalSex]]; [items addObject: [[ORKFormItemalloc]initWithIdentifier:kGenderItemIdentifier text:@"性別" answerFormat:genderFormat]; //包含了一個章節分隔符 [items addObject: [[ORKFormItemalloc]initWithSectionTitle:@"基本信息"]]; ORKAnswerFormat*bloodTypeFormat= [ORKHealthKitCharacteristicTypeAnswerFormat answerFormatWithCharacteristicType: [HKCharacteristicType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierBloodType]]; [items addObject: [[ORKFormItemalloc]initWithIdentifier:kBloodTypeItemIdentifier text:@"血型" answerFormat:bloodTypeFormat]; ORKAnswerFormat*dateOfBirthFormat= [ORKHealthKitCharacteristicTypeAnswerFormat answerFormatWithCharacteristicType: [HKCharacteristicType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierDateOfBirth]]; ORKFormItem*dateOfBirthItem= [[ORKFormItemalloc]initWithIdentifier:kDateOfBirthItemIdentifier text:@"出生日期" answerFormat:dateOfBirthFormat]; dateOfBirthItem.placeholder=@"出生日期"; [items addObject:dateOfBirthItem]; //...諸如此類,添加額外的項目 step.formItems=items;
SWIFT
guardletgenderType=HKCharacteristicType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierBiologicalSex), bloodType=HKCharacteristicType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierBloodType), birthType=HKCharacteristicType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierDateOfBirth) else{return} let step=ORKFormStep(identifier:Identifiers.Form.rawValue, title:"表單", text:"單頁展示含有多個輸入框的表單組") let genderFormat=ORKHealthKitCharacteristicTypeAnswerFormat(characteristicType:genderType) let genderItem=ORKFormItem(identifier:Identifiers.GenderItem.rawValue, text:"性別", answerFormat:genderFormat) let bloodFormat=ORKHealthKitCharacteristicTypeAnswerFormat(characteristicType:bloodType) let bloodItem=ORKFormItem(identifier:Identifiers.BloodTypeItem.rawValue, text:"血型", answerFormat:bloodFormat) let birthFormat=ORKHealthKitCharacteristicTypeAnswerFormat(characteristicType:birthType) let birthItem=ORKFormItem(identifier:Identifiers.DateOfBirthItem.rawValue, text:"出生日期", answerFormat:birthFormat) birthItem.placeholder="出生日期" letsectionItem=ORKFormItem(sectionTitle:"基本信息") //...諸如此類,添加額外的項目 step.formItems=[genderItem,sectionItem,bloodItem,birthItem]
上述代碼會生成類似於以下的界面:
表單步驟示例
回答格式
在 ResearchKit框架中,回答格式 (Answer Format) 決定了用戶應該如何回答被詢問的問題,或者如何回答表單中項目的問題。例如,試想有這樣一個調查問題:“從 1 到 10,您所感受到的疼痛是多少?”。這個問題的回答格式自然而然應該是一個處於這個范圍之內的不連續標度,因此您可以使用標度回答格式 (ORKScaleAnswerFormat),然後設置其 minimum 以及 maximum 屬性來獲得所需的范圍。
下方的截圖展示了 ResearchKit 框架提供的標准回答格式。
除了前面所說的回答格式之外,ResearchKit 框架還提供了特殊的回答格式,以便能夠詢問用戶可能已經存儲在“健康”應用當中的數據或者體征。當使用 HealthKit 回答格式的時候,任務視圖控制器會自動彈出一個健康數據訪問請求窗口給用戶(如果您的應用還沒有獲取這方面信息的授權的話)。展示的詳情信息將會被自動填充,並且,如果用戶已經授予訪問權限的話,這個字段默認將會從用戶的健康數據庫中獲取當前值。
2. 創建調查任務
一旦您創建完一個或多個步驟之後,創建一個 ORKOrderedTask 對象來持有這些步驟。下方的代碼展示了將一個布爾步驟添加到任務當中的過程。
OBJECTIVE-C
//創建一個准備包含在任務當中的是非步驟 ORKStep*booleanStep= [[ORKQuestionStepalloc]initWithIdentifier:kNutritionIdentifier]; booleanStep.title=@"您是否在服用保健品?"; booleanStep.answerFormat=[ORKBooleanAnswerFormat new]; booleanStep.optional=NO; //創建一個含有此步驟的任務 ORKOrderedTask*task= [[ORKOrderedTask alloc]initWithIdentifier:kTaskIdentifier steps:@[booleanStep]];
SWIFT
//創建一個准備包含在任務當中的是非步驟 let booleanStep=ORKQuestionStep(identifier:Identifiers.Nutrition.rawValue) booleanStep.title="您是否在服用保健品?" booleanStep.answerFormat=ORKBooleanAnswerFormat() booleanStep.optional=false //創建一個含有此步驟的任務 let task=ORKOrderedTask(identifier:Identifiers.Task.rawValue,steps:[booleanStep])
您必須要為每個步驟設置一個字符串標識符。步驟標識符在任務當中應當是獨一無二的,因為它是將任務層次中的步驟與結果層次中的步驟結果連接起來的關鍵所在。
要展示這個任務的話,將其賦給一個任務視圖控制器,然後展示這個控制器。下方的代碼展示了如何創建一個任務視圖控制器,然後用 Modal 方式將其展示出來。
OBJECTIVE-C
//使用任務來創建一個任務視圖控制器,然後設置委托 ORKTaskViewController*taskViewController= [[ORKTaskViewController alloc]initWithTask:task taskRunUUID:nil]; taskViewController.delegate=self; //展示該任務視圖控制器 [selfpresentViewController:taskViewController animated:YES completion:nil];
SWIFT
//使用任務來創建一個任務視圖控制器,然後設置委托 let taskViewController = ORKTaskViewController(task: task, taskRunUUID: nil) taskViewController.delegate = self //展示該視圖任務控制器 presentViewController(taskViewController, animated: true, completion: nil)
注意:ORKOrderedTask 假定您始終會展示所有的問題,並且永遠不會根據上一個問題的回答來決定下一個需要展示的問題。要引入這方面的條件邏輯的話,您需要繼承 ORKOrderedTask,或者自行實現 ORKTask 協議來完成這個步驟。
3. 收集結果
任務視圖控制器的 result 屬性將會返回任務的結果。每個用戶查看的步驟視圖控制器都會生成一個步驟結果 (ORKStepResult)。只要用戶在任務當中前往下一個步驟的時候,任務視圖控制器就會收集這些結果,這樣便可以生成一個 ORKTaskResult。
任務結果和步驟結果都是結果的集合,在其中可以包含其他的結果對象。例如,任務結果可以包含一個步驟結果的數組。
包含在步驟結果當中的結果類型取決於步驟的類型。例如,問題步驟將會生成一個問題結果 (ORKQuestionResult);表單步驟將會為每一個表單項目生成一個問題步驟;然後帶有記錄器的活動任務通常都會為每個記錄器生成一個結果。
結果的層級與任務和步驟的輸入模型層級非常契合,如下圖所見:
結果層級的示例
除了這些屬性之外,每個結果都還包含有一個標識符。這個標識符用以連接結果和生成該結果的模型對象(任務、步驟、表單項目,或者記錄器)。每個結果還包含了起止時間,您可以分別通過 startDate 和 endDate 屬性來使用。這些屬性可以用來推斷用戶在回答這個步驟中花費了多長時間。
決定下一個步驟的步驟結果
有些時候,在展示下一個步驟之前知曉當前步驟的結果是很重要的。例如,試想有這樣一個步驟在詢問:“您當前有發熱症狀麼?”。如果用戶回答“ Yes”,那麼下一個問題可能會是“您當前的體溫是多少?”;否則的話就可能會是“您還有其他的健康問題嗎?”。
為了在您的任務當中增加自定義條件行為,您可以使用有序任務 (ORKOrderedTask) 或者可導航的有序任務 (ORKNavigableOrderedTask),然後重載特定的 ORKTask 方法,比如說 stepAfterStep:withResult 以及 stepBeforeStep:withResult: ,這些方法都需要調用 super 方法。
有序任務
諸如調查或者活動任務之類的順序(靜態)任務,可以用有序任務來表示。
下面的這個例子演示了如何繼承 ORKOrderedTask,從而能夠根據用戶對某個是非問題的回答來提供不同的步驟集。雖然代碼展示的是步進方法,但是相應的“步進”實現通常也是必要的。
OBJECTIVE-C
- ORKStep*)stepAfterStep:(ORKStep*)step withResult:(id)result{ NSString *identifier = step.identifier; if ([identifier isEqualToString:self.qualificationStep.identifier]) { ORKStepResult *stepResult = [result stepResultForStepIdentifier:identifier]; ORKQuestionResult *result = (ORKQuestionResult *)stepResult.firstResult; if ([result isKindOfClass:[ORKBooleanQuestionResult class]]) { ORKBooleanQuestionResult *booleanResult = result; NSNumber *booleanAnswer = booleanResult.booleanAnswer; if (booleanAnswer) { return booleanAnswer.boolValue self.regularQuestionStep : self.terminationStep; } } } return [super stepAfterStep:step withResult:result]; }
SWIFT
func stepAfterStep(step: ORKStep, withResult result: ORKTaskResultSource) -> ORKStep { let identifier = step.identifier let stepResult = result.stepResultForStepIdentifier(identifier) if let result = stepResult .firstResult as ORKBooleanQuestionResult, answer = result.booleanAnswer where identifier == qualificationStep.identifier { return answer.boolValue regularQuestionStep : terminationStep } return super.stepAfterStep(step, withResult: result) }
可導航的有序任務
可導航的有序任務 (ORKNavigableOrderedTask) 從有序任務 (ORKOrderedTask) 繼承了相關的行為。除了從有序任務繼承相關的行為之後,它還提供了根據用戶對問題的回答來展示不同步驟組的功能。
當用戶在任務中的不同步驟間進行導航的時候,您可以通過添加一個條件步驟導航來添加條件。例如,當用戶從當前步驟前往下一個步驟的時候,添加一個導航規則來獲取新的目標步驟。您不能在相同的步驟中添加多個導航規則。如果您添加了多個導航規則的話,那麼最新的那個規則會替代其他規則運行。
例如,如果要僅當用戶對上一個問題的回答是“是”時,才展示調查問題的話,您可以使用 ORKPredicateStepNavigationRule;或者如果您想要在兩個步驟之間定義一個隨機跳轉的話,您可以使用 ORKDirectStepNavigationRule。
下面這個例子演示了您如何添加一個導航規則,以便能夠根據用戶對症狀類型的選擇,在任務中前往不同的步驟。例如,在“症狀”步驟中當用戶沒有選擇“頭痛”的時候,就前往“其他症狀”步驟。否則,默認的話將前往下一個步驟(應用常規的有序任務(ORKOrderedTask) applies)。
OBJECTIVE-C
ORKNavigableOrderedTask *task = [[ORKNavigableOrderedTask alloc] initWithIdentifier:StepNavigationTaskIdentifier steps:steps]; // 構建一個導航規則 ORKPredicateStepNavigationRule *predicateRule = nil; NSPredicate *predicateHeadache = [ORKResultPredicate predicateForChoiceQuestionResultWithResultIdentifier:@"symptom" expectedString:@"headache"]; // 用戶在症狀步驟中沒有選擇“頭痛” NSPredicate *predicateNotHeadache = [NSCompoundPredicate notPredicateWithSubpredicate:predicateHeadache]; predicateRule = [[ORKPredicateStepNavigationRule alloc] initWithResultPredicates:@[ predicateNotHeadache ] destinationStepIdentifiers:@[ @"other_symptom" ] ]; [task setNavigationRule:predicateRule forTriggerStepIdentifier:@"symptom"];
SWIFT
let task = ORKNavigableOrderedTask(identifier: Identifiers.StepNavigationTask.rawValue, steps: steps) // 構建一個導航規則 let resultSelector = ORKResultSelector(resultIdentifier: "symptom") let predicateHeadache = ORKResultPredicate.predicateForChoiceQuestionResultWithResultSelector(resultSelector, expectedAnswerValue: "headache") // 用戶在症狀步驟中沒有選擇“頭痛” let predicateNotHeadache = NSCompoundPredicate(notPredicateWithSubpredicate: predicateHeadache) let predicateRule = ORKPredicateStepNavigationRule(resultPredicatesAndDestinationStepIdentifiers: [(predicateNotHeadache, "other_symptom")]) task.setNavigationRule(predicateRule, forTriggerStepIdentifier: "symptom")
在任務完成後存儲結果
當任務完成後,您可以將結果進行存儲或者上傳。實現的方法包括了將某些表單中的結果層級進行序列化,或者使用內置的 NSSecureCoding 支持,也可以使用您應用所支持的其他格式化方式。
如果您的任務可以生成文件輸出的話,那麼文件通常都是被一個 ORKFileResult 對象所引用的,它們將會放置於您在任務視圖控制器當中所設置的輸出目錄當中。當您完成任務之後,其中一個實現可以是將結果層級序列化到輸出目錄當中,然後將整個輸出目錄壓縮,然後將其共享出去。
在下列示例當中,當任務成功完成之後,結果會通過 NSKeyedArchiver 進行歸檔。如果您選擇支持存儲並恢復任務現場的話,用戶或許會將任務進行存儲,因此該示例同樣也演示了如何獲得恢復後的數據,然後這個數據隨後可能會被用於恢復任務展示。
OBJECVTIVE-C
- (void)taskViewController:(ORKTaskViewController *)taskViewController didFinishWithReason:(ORKTaskViewControllerFinishReason)reason error:(NSError *)error { switch (reason) { case ORKTaskViewControllerFinishReasonCompleted: // 首先將結果對象進行歸檔 NSData *data = [NSKeyedArchiver archivedDataWithRootObject:taskViewController.result]; // 將數據存儲在硬盤上進行文件保護 // 或者安全地上傳到遠程服務器 // 如果對處理文件結果有其他需求的話 // 您也可以對 outputDirectory 進行壓縮 break; case ORKTaskViewControllerFinishReasonFailed: case ORKTaskViewControllerFinishReasonDiscarded: // 通常而言,應當丟棄該結果 // 考慮要清除輸出目錄的內容directory. break; case ORKTaskViewControllerFinishReasonSaved: NSData *data = [taskViewController restorationData]; // 持續存儲恢復的數據,以便之後可以使用 // 正常情況下,當您需要恢復的時候,不能刪除輸出目錄 break; } }
SWIFT
func taskViewController(taskViewController: ORKTaskViewController, disFinishWithReason reason: ORKTaskViewControllerFinishReason) throws { switch reason { case .Completed: // 首先將結果對象進行歸檔 let data = NSKeyedArchiver.archivedDataWithRootObject(taskViewController.result) // 將數據存儲在硬盤上進行文件保護 // 或者安全地上傳到遠程服務器 // 如果對處理文件結果有其他需求的話 // 您也可以對 outputDirectory 進行壓縮 case .Failed, .Discarded: break // 通常而言,應當丟棄該結果 // 考慮要清除輸出目錄的內容directory. case .Saved: let data = taskViewController.restorationData // 持續存儲恢復的數據,以便之後可以使用 // 正常情況下,當您需要恢復的時候,不能刪除輸出目錄 } }