Part 1, Episode 03, Start with Onboarding Task
In this episode, I want to show you how to add a single task to the app in order to show an onboarding step to the user.
Before diving into the details, let’s get an overall view of all the steps you will face when adding any tasks.
The goal is to show a single task in the TaskViewController, and that class is an OCKDailyPageViewController type.
So as you can see in the slide, there are different things that you need, to make a task; let’s begin from bottom to top.
Here I have an ID which is a single unique string that you associate it with an OCKTask known as CareKitTask.
Remember in this course when I’m saying Task, it means an OCKTask. When I make an OCKTask Object, I store it inside of my OCKSynchronizedStoreManager.
So inside the OCKDailyPageViewController, I want to add an OCKSurveyTaskViewController, then I need to have four objects for initializing it, first is the exact same ID which I’ve used for my task, second is injecting the same StoreManager as I’ve used for storing my task.
Then a query that is a simple date query and a survey which is an ORKTask known as ResearchKitTask, which I’ll explain to you how to make it in a few minutes.
From now on, when I’m talking about a survey, it means ORKTask. Let’s dig into the code now.
Here I’ve got the onboarding task in the simulator, and as you can see, it has some steps and at the end completion step. The first point to start is to Open the TaskModel and inside of the enum add your first task.
case onboarding
The next step is to open the TaskManager from the project navigator, and right after the //Make Onboarding CareKitTask fill the function like this.
let onboardingSchedule = OCKSchedule.dailyAtTime(
hour: 0,
minutes: 0,
start: Date(),
end: nil,
text: "Due date is today!",
duration: .allDay)
var onboardingTask = OCKTask(
id: TaskModel.onboarding.rawValue,
title: "Onboarding Task",
carePlanUUID: nil,
schedule: onboardingSchedule)
onboardingTask.instructions = "You have to review this task to be able to get access to the app!"
onboardingTask.impactsAdherence = false
return onboardingTask
Here I make a schedule for my onboarding task, and then I make a task using TaskModel.onboarding as an ID, and I make sure I’ve sets impactsAdherence to false since this is just an onboarding task; there won’t be any effect on the progress of each day.
The next step is to navigate to SceneDelegate and add the onboarding task to the StoreManager right after the comment.
let taskList = [TaskManager.makeOnboarding()]
Now it’s time to make your first survey. Navigate to SurveyManager as you can see; I’ve prepared some vars for you; each of these is part of your survey.
Let’s start with the first one, uncomment the welcomeInstructionStep. The welcome step is a simple ORKInstructionStep, and now you’ll need to fill up all the related variables.
let welcomeInstructionStep = ORKInstructionStep(
identifier: IdentifierModel.onboardingWelcome.rawValue
)
welcomeInstructionStep.title = "Welcome!"
welcomeInstructionStep.detailText =
"Thank you for attending to this course. Tap Next to learn more before start using."
welcomeInstructionStep.image = UIImage(named: "welcome-image")
welcomeInstructionStep.imageContentMode = .scaleAspectFill
return welcomeInstructionStep
The next is making sure you are uncommented the welcomeInstructionStep from the steps in the onboardingSurvey function.
Let’s skip other steps in the survey and continue setting up the onboarding task; we will come back to here at the end of this episode.
As I mentioned in the slide, the next step to making a task in the OCKDailyPageViewController is to make an OCKSurveyTaskViewController.
Let’s navigate to TaskViewModel, and inside of the makeTaskViewController function, you will see a comment says // Make Onboarding ViewController; since our input is a TaskModel let’s make a switch.
Then use a helper function from here and call checkIfInputTaskIsComplete, which I have provided for you, and it’s just a simple function to check if your tasks are completed or not.
switch input {
case .onboarding:
TaskViewModel.checkIfInputTaskIsComplete(input: input, storeManager: storeManager) { isComplete in
if !isComplete {
let viewController = OCKSurveyTaskViewController(
taskID: input.rawValue,
eventQuery: OCKEventQuery(for: date),
storeManager: storeManager,
survey: SurveyManager.onboardingSurvey(),
extractOutcome: { _ in [OCKOutcomeValue(Date())] })
viewController.surveyDelegate = delegate
listViewController.appendViewController(viewController, animated: false)
}
}
}
Here inside the completion handler, I check that if the task is not completed, than I can make one. I initiate a viewController by inserting the taskID and making a query for the input dates, injecting the storeManager, and using the onboardingSurvey, then assigning the delegate to the injected one and at the end append this viewController to the listViewController.
Then open the TaskViewController from the project navigator and add these codes to ask viewModel to make onboarding ViewController.
TaskViewModel.makeTaskViewController(
input: TaskModel.onboarding,
date: date,
storeManager: self.storeManager,
listViewController: listViewController,
delegate: self)
Here I call the viewModel to make the task for me by injecting all the inputs as I mentioned earlier. Let’s build and run the project to see the result. As you can see, there is only one step in the survey; let’s make more steps for the onboarding survey.
Navigate to SurveyManager and uncomment overviewInstructionStep from both places and make an ORKInstructionStep as follow.
let overviewInstructionStep = ORKInstructionStep(
identifier: IdentifierModel.onboardingOverview.rawValue
)
overviewInstructionStep.title = "Before You Start"
overviewInstructionStep.iconImage = UIImage(systemName: "checkmark.seal.fill")
let heartBodyItem = ORKBodyItem(
text: "The app will ask you to share some of your health data.",
detailText: nil,
image: UIImage(systemName: "heart.fill"),
learnMoreItem: nil,
bodyItemStyle: .image
)
let completeTasksBodyItem = ORKBodyItem(
text: "You will be asked to complete various tasks over the duration of using the app.",
detailText: nil,
image: UIImage(systemName: "checkmark.circle.fill"),
learnMoreItem: nil,
bodyItemStyle: .image
)
let signatureBodyItem = ORKBodyItem(
text: "Before joining, we will ask you to sign an informed consent document.",
detailText: nil,
image: UIImage(systemName: "signature"),
learnMoreItem: nil,
bodyItemStyle: .image
)
let secureDataBodyItem = ORKBodyItem(
text: "Your data is kept private and secure on your iPhone.",
detailText: nil,
image: UIImage(systemName: "lock.fill"),
learnMoreItem: nil,
bodyItemStyle: .image
)
overviewInstructionStep.bodyItems = [
heartBodyItem,
completeTasksBodyItem,
signatureBodyItem,
secureDataBodyItem
]
return overviewInstructionStep
}
Here I make an ORKInstructionStep that gets a title, an iconImage, and ORKBodyItem as items for the instructions. Build and run to see how it looks. The next step is to get consent from the user with a signature.
Let’s navigate to SurveyManager again and uncomment the next one, which is a webViewStep from both places and fill it up like this.
let file = Bundle.main.path(forResource: "consent", ofType: "html") ?? ""
let html = try? String(contentsOfFile: file, encoding: String.Encoding.utf8)
let consentHTML = html ?? ""
let webViewStep = ORKWebViewStep(
identifier: IdentifierModel.onboardingSignatureCapture.rawValue,
html: consentHTML
)
webViewStep.showSignatureAfterContent = true
return webViewStep
Here I load one HTML content that I’ve already added to the project, make an ORKWebViewStep, and set the showSignatureAfterContent to true to show a place for getting the signature from the user.
Let’s build and run to see the new consent step. So the next step is important; here, you get all the permissions for the app for using different things like HealthKit, Notification, and MotionActivities.
Let’s uncomment requestPermissionsStep from both places and start with HealthKit permission.
var healthKitTypesToWrite: Set<HKSampleType> = []
if
let fever = HKObjectType.categoryType(forIdentifier: .fever),
let bodyTemperature = HKObjectType.quantityType(forIdentifier: .bodyTemperature) {
healthKitTypesToWrite.insert(fever)
healthKitTypesToWrite.insert(bodyTemperature)
}
var healthKitTypesToRead: Set<HKObjectType> = []
if let fever = HKObjectType.categoryType(forIdentifier: .fever),
let bodyTemperature = HKObjectType.quantityType(forIdentifier: .bodyTemperature) {
healthKitTypesToRead.insert(fever)
healthKitTypesToRead.insert(bodyTemperature)
}
let healthKitPermissionType = ORKHealthKitPermissionType(
sampleTypesToWrite: healthKitTypesToWrite,
objectTypesToRead: healthKitTypesToRead
)
I make a set of HKSampleType for read/write data to HealthKit and, in the end, make an ORKHealthKitPermissionType.
Next, I create a NotificationPermission and MotionPermission and, last but not least, ORKRequestPermissionsStep associated with all the above permissions.
let notificationsPermissionType = ORKNotificationPermissionType(
authorizationOptions: [.alert, .badge, .sound]
)
let motionPermissionType = ORKMotionActivityPermissionType()
let requestPermissionsStep = ORKRequestPermissionsStep(
identifier: IdentifierModel.onboardingRequestPermissions.rawValue,
permissionTypes: [
healthKitPermissionType,
notificationsPermissionType,
motionPermissionType
]
)
requestPermissionsStep.title = "Health Data Request"
requestPermissionsStep.text =
"Please review the health data types below and enable sharing to contribute to the app."
return requestPermissionsStep
Here I ask different authorizationOptions for the notification, and create motionPermissionType then make an ORKRequestPermissionsStep and assign all the permission types to it.
Before running the app, you have to take care of privacy as well, which means first navigate to info.plist and then add the following options into it.
<key>NSHealthUpdateUsageDescription</key>
<string>We need to have access to your health data for the study</string>
<key>NSHealthShareUsageDescription</key>
<string>We need to have access to your health data for the study</string>
<key>NSMotionUsageDescription</key>
<string>We need to have access to your health data for the study</string>
Build and run the project to see how the permission page looks like. Remember, the permissions it’s a one-time thing, meaning that when you get the permissions, you would no longer need to review it again.
The last step is to uncomment the completion step from both places and write these codes inside it.
let completionStep = ORKCompletionStep(identifier: IdentifierModel.onboardingCompletion.rawValue)
completionStep.title = "Task Complete"
completionStep.text = "Thank you for starting this corse!"
return completionStep
Here I make a simple ORKCompletionStep with title and text as a description. Build and run the app for the last time in this episode to see how you can complete one task and remove it from your timeline.
Remember, we have store manager as an inMemory type which means every time you run the app; it starts with the empty manager.