In this article by Dhanushram Balachandran and Edward Cessna author of book Getting Started with ResearchKit, you can find the Softwareitis.xcodeproj project in the Chapter_3/Softwareitis folder of the RKBook GitHub repository (https://github.com/dhanushram/RKBook/tree/master/Chapter_3/Softwareitis).
(For more resources related to this topic, see here.)
Now that you have learned about the results of tasks from the previous section, we can modify the Softwareitis project to incorporate processing of the task results. In the TableViewController.swift file, let's update the rows data structure to include the reference for processResultsMethod: as shown in the following:
//Array of dictionaries. Each dictionary contains [ rowTitle : (didSelectRowMethod, processResultsMethod) ]
var rows : [ [String : ( didSelectRowMethod:()->(), processResultsMethod:(ORKTaskResult?)->() )] ] = []
Update the ORKTaskViewControllerDelegate method taskViewController(taskViewController:, didFinishWithReason:, error:) in TableViewController to call processResultsMethod, as shown in the following:
func taskViewController(taskViewController: ORKTaskViewController, didFinishWithReason reason: ORKTaskViewControllerFinishReason, error: NSError?)
{
if let indexPath = tappedIndexPath
{
//1
let rowDict = rows[indexPath.row]
if let tuple = rowDict.values.first
{
//2
tuple.processResultsMethod(taskViewController.result)
}
}
dismissViewControllerAnimated(true, completion: nil)
}
Now, we are ready to create our first survey. In Survey.swift, under the Surveys folder, you will find two methods defined in the TableViewController extension: showSurvey() and processSurveyResults(). These are the methods that we will be using to create the survey and process the results.
Instruction step is used to show instruction or introductory content to the user at the beginning or middle of a task. It does not produce any result as its an informational step. We can create an instruction step using the ORKInstructionStep object. It has title and detailText properties to set the appropriate content. It also has the image property to show an image.
The ORKCompletionStep is a special type of ORKInstructionStep used to show the completion of a task. The ORKCompletionStep shows an animation to indicate the completion of the task along with title and detailText, similar to ORKInstructionStep.
In creating our first Softwareitis survey, let's use the following two steps to show the information:
func showSurvey()
{
//1
let instStep = ORKInstructionStep(identifier: "Instruction Step")
instStep.title = "Softwareitis Survey"
instStep.detailText = "This survey demonstrates different question types."
//2
let completionStep = ORKCompletionStep(identifier: "Completion Step")
completionStep.title = "Thank you for taking this survey!"
//3
let task = ORKOrderedTask(identifier: "first survey", steps: [instStep, completionStep])
//4
let taskViewController = ORKTaskViewController(task: task, taskRunUUID: nil)
taskViewController.delegate = self
presentViewController(taskViewController, animated: true, completion: nil)
}
The explanation of the preceding code is as follows:
Let's update the processSurveyResults method to process the results of the instruction step and the completion step as shown in the following:
func processSurveyResults(taskResult: ORKTaskResult?)
{
if let taskResultValue = taskResult
{
//1
print("Task Run UUID : " + taskResultValue.taskRunUUID.UUIDString)
print("Survey started at : (taskResultValue.startDate!) Ended at : (taskResultValue.endDate!)")
//2
if let instStepResult = taskResultValue.stepResultForStepIdentifier("Instruction Step")
{
print("Instruction Step started at : (instStepResult.startDate!) Ended at : (instStepResult.endDate!)")
}
//3
if let compStepResult = taskResultValue.stepResultForStepIdentifier("Completion Step")
{
print("Completion Step started at : (compStepResult.startDate!) Ended at : (compStepResult.endDate!)")
}
}
}
The explanation of the preceding code is given in the following:
The preceding code produces screens as shown in the following image:
After the Done button is pressed in the completion step, Xcode prints the output that is similar to the following:
Task Run UUID : 0A343E5A-A5CD-4E7C-88C6-893E2B10E7F7
Survey started at : 2015-08-11 00:41:03 +0000 Ended at : 2015-08-11 00:41:07 +0000
Instruction Step started at : 2015-08-11 00:41:03 +0000 Ended at : 2015-08-11 00:41:05 +0000
Completion Step started at : 2015-08-11 00:41:05 +0000 Ended at : 2015-08-11 00:41:07 +0000
Question steps make up the body of a survey. ResearchKit supports question steps with various answer types such as boolean (Yes or No), numeric input, date selection, and so on.
Let's first create a question step with the simplest boolean answer type by inserting the following line of code in showSurvey():
let question1 = ORKQuestionStep(identifier: "question 1", title: "Have you ever been diagnosed with Softwareitis?", answer: ORKAnswerFormat.booleanAnswerFormat())
The preceding code creates a ORKQuestionStep object with identifier question 1, title with the question, and an ORKBooleanAnswerFormat object created using the booleanAnswerFormat() class method of ORKAnswerFormat. The answer type for a question is determined by the type of the ORKAnswerFormat object that is passed in the answer parameter. The ORKAnswerFormat has several subclasses such as ORKBooleanAnswerFormat, ORKNumericAnswerFormat, and so on. Here, we are using ORKBooleanAnswerFormat.
Don't forget to insert the created question step in the ORKOrderedTask steps parameter by updating the following line:
let task = ORKOrderedTask(identifier: "first survey", steps: [instStep, question1, completionStep])
When you run the preceding changes in Xcode and start the survey, you will see the question step with the Yes or No options. We have now successfully added a boolean question step to our survey, as shown in the following image:
Now, its time to process the results of this question step. The result is produced in an ORKBooleanQuestionResult object. Insert the following lines of code in processSurveyResults():
//1
if let question1Result = taskResultValue.stepResultForStepIdentifier("question 1")?.results?.first as? ORKBooleanQuestionResult
{
//2
if question1Result.booleanAnswer != nil
{
let answerString = question1Result.booleanAnswer!.boolValue ? "Yes" : "No"
print("Answer to question 1 is (answerString)")
}
else
{
print("question 1 was skipped")
}
}
The explanation of the preceding code is as follows:
You can disable the skipping-of-a-question step by setting its optional property to false.
We can add the numeric and scale type question steps using the following lines of code in showSurvey():
//1
let question2 = ORKQuestionStep(identifier: "question 2", title: "How many apps do you download per week?", answer: ORKAnswerFormat.integerAnswerFormatWithUnit("Apps per week"))
//2
let answerFormat3 = ORKNumericAnswerFormat.scaleAnswerFormatWithMaximumValue(10, minimumValue: 0, defaultValue: 5, step: 1, vertical: false, maximumValueDescription: nil, minimumValueDescription: nil)
let question3 = ORKQuestionStep(identifier: "question 3", title: "How many apps do you download per week (range)?", answer: answerFormat3)
The explanation of the preceding code is as follows:
The following lines in processSurveyResults() process the results from the number and the scale questions:
//1
if let question2Result = taskResultValue.stepResultForStepIdentifier("question 2")?.results?.first as? ORKNumericQuestionResult
{
if question2Result.numericAnswer != nil
{
print("Answer to question 2 is (question2Result.numericAnswer!)")
}
else
{
print("question 2 was skipped")
}
}
//2
if let question3Result = taskResultValue.stepResultForStepIdentifier("question 3")?.results?.first as? ORKScaleQuestionResult
{
if question3Result.scaleAnswer != nil
{
print("Answer to question 3 is (question3Result.scaleAnswer!)")
}
else
{
print("question 3 was skipped")
}
}
The explanation of the preceding code is as follows:
As you can see in the following image, the numeric type question generates a free form text field to enter the value, while scale type generates a slider:
Let's look at a slightly complicated question type with ORKTextChoiceAnswerFormat. In order to use this answer format, we need to create the ORKTextChoice objects before hand. Each text choice object provides the necessary data to act as a choice in a single choice or multiple choice question. The following lines in showSurvey() create a single choice question with three options:
//1
let textChoice1 = ORKTextChoice(text: "Games", detailText: nil, value: 1, exclusive: false)
let textChoice2 = ORKTextChoice(text: "Lifestyle", detailText: nil, value: 2, exclusive: false)
let textChoice3 = ORKTextChoice(text: "Utility", detailText: nil, value: 3, exclusive: false)
//2
let answerFormat4 = ORKNumericAnswerFormat.choiceAnswerFormatWithStyle(ORKChoiceAnswerStyle.SingleChoice, textChoices: [textChoice1, textChoice2, textChoice3])
let question4 = ORKQuestionStep(identifier: "question 4", title: "Which category of apps do you download the most?", answer: answerFormat4)
The explanation of the preceding code is as follows:
Processing the results from a single or multiple choice question is shown in the following. Needless to say, this code goes in the processSurveyResults() method:
//1
if let question4Result = taskResultValue.stepResultForStepIdentifier("question 4")?.results?.first as? ORKChoiceQuestionResult
{
//2
if question4Result.choiceAnswers != nil
{
print("Answer to question 4 is (question4Result.choiceAnswers!)")
}
else
{
print("question 4 was skipped")
}
}
The explanation of the preceding code is as follows:
The following image shows the generated choice question UI for the preceding code:
There are several other question types, which operate in a very similar manner like the ones we discussed so far. You can find them in the documentations of ORKAnswerFormat and ORKResult classes. The Softwareitis project has implementation of two additional types: date format and time interval format.
Using custom tasks, you can create surveys that can skip the display of certain questions based on the answers that the users have provided so far. For example, in a smoking habits survey, if the user chooses "I do not smoke" option, then the ability to not display the "How many cigarettes per day?" question.
A form step allows you to combine several related questions in a single scrollable page and reduces the number of the Next button taps for the user. The ORKFormStep object is used to create the form step. The questions in the form are represented using the ORKFormItem objects. The ORKFormItem is similar to ORKQuestionStep, in which it takes the same parameters (title and answer format).
Let's create a new survey with a form step by creating a form.swift extension file and adding the form entry to the rows array in TableViewController.swift, as shown in the following:
func setupTableViewRows()
{
rows += [
["Survey" : (didSelectRowMethod: self.showSurvey, processResultsMethod: self.processSurveyResults)],
//1
["Form" : (didSelectRowMethod: self.showForm, processResultsMethod: self.processFormResults)]
]
}
The explanation of the preceding code is as follows:
The following code shows the showForm() method in Form.swift file:
func showForm()
{
//1
let instStep = ORKInstructionStep(identifier: "Instruction Step")
instStep.title = "Softwareitis Form Type Survey"
instStep.detailText = "This survey demonstrates a form type step."
//2
let question1 = ORKFormItem(identifier: "question 1", text: "Have you ever been diagnosed with Softwareitis?", answerFormat: ORKAnswerFormat.booleanAnswerFormat())
let question2 = ORKFormItem(identifier: "question 2", text: "How many apps do you download per week?", answerFormat: ORKAnswerFormat.integerAnswerFormatWithUnit("Apps per week"))
//3
let formStep = ORKFormStep(identifier: "form step", title: "Softwareitis Survey", text: nil)
formStep.formItems = [question1, question2]
//1
let completionStep = ORKCompletionStep(identifier: "Completion Step")
completionStep.title = "Thank you for taking this survey!"
//4
let task = ORKOrderedTask(identifier: "survey with form", steps: [instStep, formStep, completionStep])
let taskViewController = ORKTaskViewController(task: task, taskRunUUID: nil)
taskViewController.delegate = self
presentViewController(taskViewController, animated: true, completion: nil)
}
The explanation of the preceding code is as follows:
The results are processed using the following processFormResults() method:
func processFormResults(taskResult: ORKTaskResult?)
{
if let taskResultValue = taskResult
{
//1
if let formStepResult = taskResultValue.stepResultForStepIdentifier("form step"), formItemResults = formStepResult.results
{
//2
for result in formItemResults
{
//3
switch result
{
case let booleanResult as ORKBooleanQuestionResult:
if booleanResult.booleanAnswer != nil
{
let answerString = booleanResult.booleanAnswer!.boolValue ? "Yes" : "No"
print("Answer to (booleanResult.identifier) is (answerString)")
}
else
{
print("(booleanResult.identifier) was skipped")
}
case let numericResult as ORKNumericQuestionResult:
if numericResult.numericAnswer != nil
{
print("Answer to (numericResult.identifier) is (numericResult.numericAnswer!)")
}
else
{
print("(numericResult.identifier) was skipped")
}
default: break
}
}
}
}
}
The explanation of the preceding code is as follows:
The following image shows the form step:
Many clinical research studies that are conducted using a pen and paper tend to have well established surveys. When you try to convert these surveys to ResearchKit, they may not convert perfectly. Some questions and answer choices may have to be reworded so that they can fit on a phone screen. You are advised to work closely with the clinical researchers so that the changes in the surveys still produce comparable results with their pen and paper counterparts. Another aspect to consider is to eliminate some of the survey questions if the answers can be found elsewhere in the user's device. For example, age, blood type, and so on, can be obtained from HealthKit if the user has already set them. This will help in improving the user experience of your app.
Here we have learned to build surveys using Xcode.
Further resources on this subject: