Compare commits

..

1 Commits

Author SHA1 Message Date
Louie 38d161f1bf Stable Public Release 2.2.15
Merging main into stable
2023-12-05 14:20:25 -08:00
1981 changed files with 163250 additions and 17213 deletions
+19 -16
View File
@@ -6,7 +6,7 @@ codebase. However, other types of contributions are welcome too, in
keeping with the ResearchKit™ framework [best practices](../../wiki/best-practices). For example,
contributions of original free-to-use survey content, back-end integrations,
validation data, and analysis or processing tools are all welcome. Ask
on the [*ResearchKit* Forum](https://developer.apple.com/forums/tags/researchkit) or [contact us](mailto:researchkit@apple.com) for guidance.
on the [*ResearchKit* Forum](https://forums.developer.apple.com/community/researchkit) or [contact us](https://developer.apple.com/contact/researchkit/) for guidance.
Contributing software
@@ -16,7 +16,8 @@ This page assumes you already know how to check out and build the
code. Contributions to the ResearchKit framework are expected to comply with the
[ResearchKit Contribution Terms and License Policy](#contribution); please familiarize yourself
with this policy prior to submitting a pull request. For any contribution, ensure that you own
the rights or have permission from the copyright holder (e.g. code, images, surveys, videos and other content you may include).
the rights or have permission from the copyright holder. (e.g. code, images, surveys, videos
and other content you may include)
To contribute to ResearchKit:
@@ -26,7 +27,7 @@ To contribute to ResearchKit:
4. [Run the tests.](#test)
5. [Submit a pull request.](#request)
6. Make any changes requested by the reviewer, and update your pull request as needed.
7. Once accepted, your pull request will be merged into main.
7. Once accepted, your pull request will be merged into master.
Choosing an issue to work on<a name="create"></a>
----------------------------
@@ -35,12 +36,13 @@ To find an issue to work on, either pick something that you need for
your app, or select one of the issues from our [issue list](../../issues). Or,
consider one of the areas where we'd like to extend ResearchKit:
* Improving the APIs needed to get started with a ResearchKit project
* New Active Tasks
* Data analysis for Active Tasks
* Backend integrations
* Faster 'get started' to a useful app
* More active tasks
* Data analysis for active tasks
* More consent sections
* Back end integrations
If in doubt, bring your idea up on the [ResearchKit Forum](https://developer.apple.com/forums/tags/researchkit).
If in doubt, bring your idea up on the [*ResearchKit* Forum](https://forums.developer.apple.com/community/researchkit).
Creating a personal fork<a name="fork"></a>
@@ -54,7 +56,8 @@ Develop your changes in your fork<a name="develop"></a>
---------------------------------
Develop your changes using your normal development process. If you
already have code from an existing project, you may need to adjust its style to more closely match the [ResearchKit framework coding style](./docs-standalone/coding-style-guide.md).
already have code from an existing project, you may need to adjust its
style to more closely match the [ResearchKit framework coding style](./docs-standalone/coding-style-guide.md).
New components may need to expose new Public or Private
headers. Public headers are for APIs that are likely to be a stable
@@ -75,7 +78,7 @@ code to other existing demo apps to exercise your feature.
When adding UI driven components, make sure that they are accessible.
Follow the steps outlined in the [Best Practices](../../wiki/best-practices)
section under Accessibility. Before submitting the pull request, you should
audit your components with VoiceOver (or other relevant assistive technologies)
audit your components with Voice Over (or other relevant assistive technologies)
enabled.
Keep changes that fix different issues separate. For bug fixes,
@@ -96,11 +99,11 @@ verify that test apps run on both device and simulator.
Where your code affects UI presentation, also test:
* Multiple device form factors (for instance, iPhone SE, iPhone 14, iPhone 15 Pro, iPhone 15 Pro Max).
* Multiple device form factors (for instance, iPhone 4S, iPhone 5, iPhone 6, iPhone 6 Plus).
* Dynamic text, especially at the "Large" setting.
* Rotation between portrait and landscape, where appropriate.
You can use the `ORKCatalog` app in the `samples` directory to
You can use the apps in the `Testing` and `samples` directories to
test your changes.
Submit a pull request<a name="request"></a>
@@ -117,7 +120,7 @@ After acceptance<a name="after"></a>
----------------
Once your pull request has been accepted, your changes will be merged
to main. You are still responsible for your change after it is
to master. You are still responsible for your change after it is
accepted. Stay in contact, in case bugs are detected that may require
your attention.
@@ -131,7 +134,7 @@ documentation, or other issues during this process.
Release process
-----------------
The `main` branch is used for work in progress. On `main`:
The `master` branch is used for work in progress. On `master`:
* All test apps should build and run error free.
* Unit tests should all pass.
@@ -139,9 +142,9 @@ The `main` branch is used for work in progress. On `main`:
base language).
The project will make periodic releases. When preparing a stable release, we
will branch from `main` to a convergence branch. During this process,
will branch from `master` to a convergence branch. During this process,
changes will be made first to the convergence branch, and then merged into
`main`. On the convergence branch, changes will be made only to:
`master`. On the convergence branch, changes will be made only to:
* Fix high priority issues.
* Update documentation.
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2015-2024, Apple Inc. All rights reserved.
Copyright (c) 2015, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
+262 -139
View File
@@ -6,170 +6,293 @@ ResearchKit Framework
The *ResearchKit™ framework* is an open source software framework that makes it easy to create apps
for medical research or for other research projects.
# Table of Contents
* [Requirements](#requirements)
* [Documentation](#documentation)
* [Getting Started](#gettingstarted)
* [Installing](#installation)
* [ORKCatalog App](#orkcatalog-app)
* [Surveys](#surveys)
* [Consent](#consent)
* [Active Tasks](#active-tasks)
* [Getting Help](#getting-help)
* [License](#license)
# Requirements <a name="requirements"></a>
The *ResearchKit framework* codebase supports iOS and requires Xcode 12.0 or newer. The *ResearchKit framework* has a Base SDK version of 13.0.
# Documentation <a name="documentation"></a>
<img width="1000" alt="ebedded-framework" src="https://github.com/ResearchKit/ResearchKit/assets/29615893/19d6edd3-3d95-4416-9ac4-24ccb35e09c2">
View the *ResearchKit framework* documentation by setting ResearchKit as your target in Xcode and selecting 'Build Documentation' in the Product menu dropdown.
# Getting Started <a name="gettingstarted"></a>
* [Documentation](docs/)
* [Best Practices](../../wiki/best-practices)
* [Contributing to ResearchKit](CONTRIBUTING.md)
* [Website](https://www.researchandcare.org)
* [WWDC: ResearchKit and CareKit Reimagined](https://developer.apple.com/videos/play/wwdc2019/217/)
* [ResearchKit BSD License](#license)
Getting More Information
========================
### Install as an embedded framework <a name="installation"></a>
* Join the [*ResearchKit* Forum](https://developer.apple.com/forums/tags/researchkit) for discussing uses of the *ResearchKit framework and* related projects.
Download the project source code and drag in ResearchKit.xcodeproj. Then, embed *ResearchKit* framework in your app by adding it to the "Frameworks, Libraries, and Embedded Content" section for your target as shown in the figure below.
Use Cases
===========
<img width="1000" alt="ebedded-framework" src="https://github.com/ResearchKit/ResearchKit/assets/29615893/7479f313-ecc7-4d94-8c64-c58ae7362a4d">
A task in the *ResearchKit framework* contains a set of steps to present to a user. Everything,
whether its a *survey*, the *consent process*, or *active tasks*, is represented as a task that can
be presented with a task view controller.
### ORKCatalog App <a name="orkcatalog-app"></a>
The included catalog app demonstrates the different modules that are available in *ResearchKit*. Find the
project in ResearchKit's [`samples`](samples) directory.
| | |
|---|---|
| ![catalog-home](https://github.com/ResearchKit/ResearchKit/assets/29615893/45357cf8-17bf-4f38-aebc-bdf1c3395eb5) | ![catalog-survey](https://github.com/ResearchKit/ResearchKit/assets/29615893/a850f20b-7a05-4d14-bc2d-2d6dab7af30d) |
# Surveys <a name="surveys"></a>
Surveys
-------
The *ResearchKit framework* provides a pre-built user interface for surveys, which can be presented
modally on an *iPhone* or *iPad*. The example below shows the process to present a height question for a participant to answer.
modally on an *iPhone*, *iPod Touch*, or *iPad*. See
*[Creating Surveys](docs/Survey/)* for more
information.
```swift
import ResearchKit
import ResearchKitUI
let sectionHeaderFormItem = ORKFormItem(sectionTitle: "Your question here.")
let heightQuestionFormItem = ORKFormItem(identifier: "heightQuestionFormItem1", text: nil, answerFormat: ORKAnswerFormat.heightAnswerFormat())
heightQuestionFormItem.placeholder = "Tap here"
Consent
----------------
let formStep = ORKFormStep(identifier: "HeightQuestionIdentifier", title: "Height", text: "Local system")
formStep.formItems = [sectionHeaderFormItem, heightQuestionFormItem]
The *ResearchKit framework* provides visual consent templates that you can customize to explain the
details of your research study and obtain a signature if needed.
See *[Obtaining Consent](docs/InformedConsent/)* for
more information.
return formStep
```
The height question is presented in the figure below.
| | |
|---|---|
| ![height-question](https://github.com/ResearchKit/ResearchKit/assets/29615893/4f425329-83b7-45c3-84f9-58cdbcaf2529) | ![height-question-2](https://github.com/ResearchKit/ResearchKit/assets/29615893/2cc0dc2c-5c2a-4b50-a4be-834363fb64b5) |
# Consent <a name="consent"></a>
The *ResearchKit framework* provides classes that you can customize to explain the
details of your research study and obtain a signature if needed. Use *ResearchKit's* provided classes to quickly welcome, and inform your participants of what the study entails.
```swift
import ResearchKit
import ResearchKitUI
// Welcome page.
let welcomeStep = ORKInstructionStep(identifier: String(describing: Identifier.consentWelcomeInstructionStep))
welcomeStep.iconImage = UIImage(systemName: "hand.wave")
welcomeStep.title = "Welcome!"
welcomeStep.detailText = "Thank you for joining our study. Tap Next to learn more before signing up."
// Before You Join page.
let beforeYouJoinStep = ORKInstructionStep(identifier: String(describing: Identifier.informedConsentInstructionStep))
beforeYouJoinStep.iconImage = UIImage(systemName: "doc.text.magnifyingglass")
beforeYouJoinStep.title = "Before You Join"
let sharingHealthDataBodyItem = ORKBodyItem(text: "The study will ask you to share some of your Health data.",
detailText: nil,
image: UIImage(systemName: "heart.fill"),
learnMoreItem: nil,
bodyItemStyle: .image)
let completingTasksBodyItem = ORKBodyItem(text: "You will be asked to complete various tasks over the duration of the study.",
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.",
detailText: nil,
image: UIImage(systemName: "lock.fill"),
learnMoreItem: nil,
bodyItemStyle: .image)
beforeYouJoinStep.bodyItems = [
sharingHealthDataBodyItem,
completingTasksBodyItem,
signatureBodyItem,
secureDataBodyItem
]
```
The consent steps are presented in the figure below.
| | |
|---|---|
| ![consent-welcome-page](https://github.com/ResearchKit/ResearchKit/assets/29615893/e6cbbe07-47ed-4bb4-a84a-f3bf612e9122) | ![consent-before-you-join](https://github.com/ResearchKit/ResearchKit/assets/29615893/687fe345-14d9-4356-9c37-c6a2714875ae) |
Vist the `Obtaining Consent`article in ResearchKit's Documentation for
more examples that include signature collection and PDF file storage.
# Active Tasks <a name="active-tasks"></a>
Active Tasks
------------
Some studies may need data beyond survey questions or the passive data collection capabilities
available through use of the *HealthKit* and *CoreMotion* APIs if you are programming for *iOS*.
*ResearchKit*'s active tasks invite users to perform activities under semi-controlled conditions,
while *iPhone* sensors actively collect data.
while *iPhone* sensors actively collect data. See
*[Active Tasks](docs/ActiveTasks/)* for more
information.
ResearchKit active tasks are not diagnostic tools nor medical devices of any kind and output from those active tasks may not be used for diagnosis. Developers and researchers are responsible for complying with all applicable laws and regulations with respect to further development and use of the active tasks.
Use predefined tasks provided by *ResearchKit* to guide your participants through specific actions.
Charts
------------
*ResearchKit* includes a *Charts module*. It features three chart types: a *pie chart* (`ORKPieChartView`), a *line graph chart* (`ORKLineGraphChartView`), and a *discrete graph chart* (`ORKDiscreteGraphChartView`).
The views in the *Charts module* can be used independently of the rest of *ResearchKit*. They don't automatically connect with any other part of *ResearchKit*: the developer has to supply the data to be displayed through the views' `dataSources`, which allows for maximum flexibility.
Getting Started<a name="gettingstarted"></a>
===============
Requirements
------------
The primary *ResearchKit framework* codebase supports *iOS* and requires *Xcode 8.0* or newer. The
*ResearchKit framework* has a *Base SDK* version of *8.0*, meaning that apps using the *ResearchKit
framework* can run on devices with *iOS 8.0* or newer.
Installation
------------
The latest stable version of *ResearchKit framework* can be cloned with
```
git clone -b stable https://github.com/ResearchKit/ResearchKit.git
```
Or, for the latest changes, use the `main` branch:
```
git clone https://github.com/ResearchKit/ResearchKit.git
```
CocoaPods Installation
------------
For latest stable release
```
pod 'ResearchKit'
```
For early development releases
```
pod 'ResearchKit', :git => 'https://github.com/ResearchKit/ResearchKit.git', :branch => 'main'
```
Building
--------
Build the *ResearchKit framework* by opening `ResearchKit.xcodeproj` and running the `ResearchKit`
framework target. Optionally, run the unit tests too.
Adding the ResearchKit framework to your App
------------------------------
This walk-through shows how to embed the *ResearchKit framework* in your app as a dynamic framework,
and present a simple task view controller.
### 1. Add the ResearchKit framework to Your Project
To get started, drag `ResearchKit.xcodeproj` from your checkout into your *iOS* app project
in *Xcode*:
<center>
<figure>
<img src="../../wiki/AddingResearchKitXcode.png" alt="Adding the ResearchKit framework to your
project" align="middle"/>
</figure>
</center>
Then, embed the *ResearchKit framework* as a dynamic framework in your app, by adding it to the
*Embedded Binaries* section of the *General* pane for your target as shown in the figure below.
<center>
<figure>
<img src="../../wiki/AddedBinaries.png" width="100%" alt="Adding the ResearchKit framework to
Embedded Binaries" align="middle"/>
<figcaption><center>Adding the ResearchKit framework to Embedded Binaries</center></figcaption>
</figure>
</center>
Note: You can also import *ResearchKit* into your project using a
[dependency manager](./docs-standalone/dependency-management.md) such as *CocoaPods* or *Carthage*.
### 2. Create a Step
In this walk-through, we will use the *ResearchKit framework* to modally present a simple
single-step task showing a single instruction.
Create a step for your task by adding some code, perhaps in `viewDidAppear:` of an existing view
controller. To keep things simple, we'll use an instruction step (`ORKInstructionStep`) and name
the step `myStep`.
*Objective-C*
```objc
ORKInstructionStep *myStep =
[[ORKInstructionStep alloc] initWithIdentifier:@"intro"];
myStep.title = @"Welcome to ResearchKit";
```
*Swift*
```swift
import ResearchKit
import ResearchKitUI
import ResearchKitActiveTask
let orderedTask = ORKOrderedTask.dBHLToneAudiometryTask(withIdentifier: "dBHLToneAudiometryTaskIdentifier",
intendedUseDescription: nil, options: [])
let taskViewController = ORKTaskViewController(task: orderedTask, taskRun: nil)
taskViewController.delegate = self
present(taskViewController, animated: true)
let myStep = ORKInstructionStep(identifier: "intro")
myStep.title = "Welcome to ResearchKit"
```
The dBHL Tone Audiometry task is presented in the figure below.
| | |
|---|---|
| ![noise-check](https://github.com/ResearchKit/ResearchKit/assets/29615893/d8fb669c-bb60-482d-9a2d-e5b6b6696aa5) | ![dbhl-tone-test](https://github.com/ResearchKit/ResearchKit/assets/29615893/04df862b-46bc-4749-8c3e-02d2e54dbcbf) |
### 3. Create a Task
# Getting Help <a name="getting-help"></a>
Use the ordered task class (`ORKOrderedTask`) to create a task that contains `myStep`. An ordered
task is just a task where the order and selection of later steps does not depend on the results of
earlier ones. Name your task `task` and initialize it with `myStep`.
GitHub is our primary forum for ResearchKit. Feel free to open up issues about questions, problems, or ideas.
*Objective-C*
# License <a name="license"></a>
```objc
ORKOrderedTask *task =
[[ORKOrderedTask alloc] initWithIdentifier:@"task" steps:@[myStep]];
```
This project is made available under the terms of a BSD license. See the [LICENSE](LICENSE) file.
*Swift*
```swift
let task = ORKOrderedTask(identifier: "task", steps: [myStep])
```
### 4. Present the Task
Create a task view controller (`ORKTaskViewController`) and initialize it with your `task`. A task
view controller manages a task and collects the results of each step. In this case, your task view
controller simply displays your instruction step.
*Objective-C*
```objc
ORKTaskViewController *taskViewController =
[[ORKTaskViewController alloc] initWithTask:task taskRunUUID:nil];
taskViewController.delegate = self;
[self presentViewController:taskViewController animated:YES completion:nil];
```
*Swift*
```swift
let taskViewController = ORKTaskViewController(task: task, taskRun: nil)
taskViewController.delegate = self
present(taskViewController, animated: true, completion: nil)
```
The above snippet assumes that your class implements the `ORKTaskViewControllerDelegate` protocol.
This has just one required method, which you must implement in order to handle the completion of
the task:
*Objective-C*
```objc
- (void)taskViewController:(ORKTaskViewController *)taskViewController
didFinishWithReason:(ORKTaskViewControllerFinishReason)reason
error:(NSError *)error {
ORKTaskResult *taskResult = [taskViewController result];
// You could do something with the result here.
// Then, dismiss the task view controller.
[self dismissViewControllerAnimated:YES completion:nil];
}
```
*Swift*
```swift
func taskViewController(_ taskViewController: ORKTaskViewController,
didFinishWith reason: ORKTaskViewControllerFinishReason,
error: Error?) {
let taskResult = taskViewController.result
// You could do something with the result here.
// Then, dismiss the task view controller.
dismiss(animated: true, completion: nil)
}
```
If you now run your app, you should see your first *ResearchKit framework* instruction step:
<center>
<figure>
<img src="../../wiki/HelloWorld.png" width="50%" alt="HelloWorld example screenshot" align="middle"/>
</figure>
</center>
What else can the ResearchKit framework do?
-----------------------------
The *ResearchKit* [`ORKCatalog`](samples/ORKCatalog) sample app is a good place to start. Find the
project in ResearchKit's [`samples`](samples) directory. This project includes a list of all the
types of steps supported by the *ResearchKit framework* in the first tab, and displays a browser for the
results of the last completed task in the second tab. The third tab shows some examples from the *Charts module*.
License<a name="license"></a>
=======
The source in the *ResearchKit* repository is made available under the following license unless
another license is explicitly identified:
```
Copyright (c) 2015 - 2018, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors
may be used to endorse or promote products derived from this software without
specific prior written permission. No license is granted to the trademarks of
the copyright holders even if such marks are included in this software.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```
-28
View File
@@ -1,33 +1,5 @@
# ResearchKit Release Notes
## ResearchKit 3.0 Release Notes
*ResearchKit 3.0* is a beta release
In addition to general stability and performance improvements, ResearchKit 3.0 includes the following updates.
### Framework Updates
In order to modularize ResearchKit we have separated its functionality into the following modules.
- **ResearchKit**
contains the core classes and objects needed to run ResearchKit in any environment.
- **ResearchKitUI**
contains the UI classes and objects needed to present ResearchKit views in an IOS environment.
- **ResearchKitActiveTask**
contains the classes and objects needed to present Active Tasks in an IOS enviroment. These tasks usually require access to device sensors.
### Future Deprecations
The following APIs will be labeled deprecated spring of 2025.
- **Consent**
Our `ORKConsent` APIs will be deprecated in favor of using existing functionality (e.g `ORKInstructionStep` and `ORKWebViewStep`) to achieve informed consent. Please visit the `ORKCatalog` app to view a recommended example.
- **Question Step**
The `ORKQuestionStep` will be deprecated in favor of using the `ORKFormStep`. Certain answer formats will present slightly different UI when used with a `ORKFormStep`. Updates will be made to ensure backwards compatibility before the question step is removed.
## ResearchKit 2.0 Release Notes
*ResearchKit 2.0* supports *iOS* and requires *Xcode 9.0* or newer.
+3
View File
@@ -4,6 +4,9 @@
<FileRef
location = "group:ResearchKit.xcodeproj">
</FileRef>
<FileRef
location = "group:Testing/ORKTest/ORKTest.xcodeproj">
</FileRef>
<FileRef
location = "group:samples/ORKCatalog/ORKCatalog.xcodeproj">
</FileRef>
+18
View File
@@ -0,0 +1,18 @@
Pod::Spec.new do |s|
s.name = 'ResearchKit'
s.version = '2.2.15'
s.summary = 'ResearchKit is an open source software framework that makes it easy to create apps for medical research or for other research projects.'
s.homepage = 'https://www.github.com/ResearchKit/ResearchKit'
s.documentation_url = 'http://researchkit.github.io/docs/'
s.license = { :type => 'BSD', :file => 'LICENSE' }
s.author = { 'researchkit.org' => 'http://researchkit.org' }
s.source = { :git => 'https://github.com/ResearchKit/ResearchKit.git', :tag => s.version.to_s }
s.public_header_files = `./scripts/find_headers.rb --public`.split("\n")
s.private_header_files = `./scripts/find_headers.rb --private`.split("\n")
s.source_files = 'ResearchKit/**/*.{h,m,swift}'
s.resources = 'ResearchKit/**/*.{fsh,vsh}', 'ResearchKit/Animations/**/*.m4v', 'ResearchKit/Artwork.xcassets', 'ResearchKit/Localized/*.lproj'
s.platform = :ios, '11.0'
s.requires_arc = true
s.swift_version = '5'
s.module_map = "ResearchKit/ResearchKit.modulemap"
end
File diff suppressed because it is too large Load Diff
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
LastUpgradeVersion = "1310"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
@@ -15,11 +15,25 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B183A4731A8535D100C76870"
BuildableName = ".framework"
BuildableName = "ResearchKit.framework"
BlueprintName = "ResearchKit"
ReferencedContainer = "container:ResearchKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "86CC8E991AC09332001CCD89"
BuildableName = "ResearchKitTests.xctest"
BlueprintName = "ResearchKitTests"
ReferencedContainer = "container:ResearchKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
@@ -51,7 +65,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B183A4731A8535D100C76870"
BuildableName = ".framework"
BuildableName = "ResearchKit.framework"
BlueprintName = "ResearchKit"
ReferencedContainer = "container:ResearchKit.xcodeproj">
</BuildableReference>
@@ -67,7 +81,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B183A4731A8535D100C76870"
BuildableName = ".framework"
BuildableName = "ResearchKit.framework"
BlueprintName = "ResearchKit"
ReferencedContainer = "container:ResearchKit.xcodeproj">
</BuildableReference>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
LastUpgradeVersion = "1310"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -33,7 +33,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B183A4731A8535D100C76870"
BuildableName = ".framework"
BuildableName = "ResearchKit.framework"
BlueprintName = "ResearchKit"
ReferencedContainer = "container:ResearchKit.xcodeproj">
</BuildableReference>
@@ -50,11 +50,6 @@
BlueprintName = "ResearchKitTests"
ReferencedContainer = "container:ResearchKit.xcodeproj">
</BuildableReference>
<SkippedTests>
<Test
Identifier = "ORKDataCollectionTests">
</Test>
</SkippedTests>
</TestableReference>
</Testables>
</TestAction>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1340"
LastUpgradeVersion = "1310"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -14,9 +14,9 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CA1C7A40288B0C68004DAB3A"
BuildableName = "ResearchKitUI.framework"
BlueprintName = "ResearchKitUI (iOS)"
BlueprintIdentifier = "B18FF3A41A9FE25700C0C3B0"
BuildableName = "docs"
BlueprintName = "docs"
ReferencedContainer = "container:ResearchKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@@ -40,6 +40,15 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B18FF3A41A9FE25700C0C3B0"
BuildableName = "docs"
BlueprintName = "docs"
ReferencedContainer = "container:ResearchKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -50,9 +59,9 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CA1C7A40288B0C68004DAB3A"
BuildableName = "ResearchKitUI.framework"
BlueprintName = "ResearchKitUI (iOS)"
BlueprintIdentifier = "B18FF3A41A9FE25700C0C3B0"
BuildableName = "docs"
BlueprintName = "docs"
ReferencedContainer = "container:ResearchKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
-3
View File
@@ -13,9 +13,6 @@
},
"testTargets" : [
{
"skippedTests" : [
"ORKDataCollectionTests"
],
"target" : {
"containerPath" : "container:ResearchKit.xcodeproj",
"identifier" : "86CC8E991AC09332001CCD89",
@@ -30,6 +30,6 @@
// Shared header for accessibility functionality.
#import <ResearchKitUI/ORKAccessibilityFunctions.h>
#import <ResearchKitUI/ORKGraphChartAccessibilityElement.h>
#import <ResearchKitUI/UIView+ORKAccessibility.h>
#import <ResearchKit/ORKAccessibilityFunctions.h>
#import <ResearchKit/ORKGraphChartAccessibilityElement.h>
#import <ResearchKit/UIView+ORKAccessibility.h>
@@ -51,30 +51,6 @@ ORK_INLINE void ORKAccessibilityPostNotificationAfterDelay(UIAccessibilityNotifi
});
}
// Convenience for checking if accessibility large text is enabled
ORK_INLINE BOOL ORKIsAccessibilityLargeTextEnabled(void) {
NSString *contentSize = [UIApplication sharedApplication].preferredContentSizeCategory;
if ([@[UIContentSizeCategoryExtraExtraExtraLarge,
UIContentSizeCategoryAccessibilityExtraLarge,
UIContentSizeCategoryAccessibilityMedium, UIContentSizeCategoryAccessibilityLarge,
UIContentSizeCategoryAccessibilityExtraExtraLarge,
UIContentSizeCategoryAccessibilityExtraExtraExtraLarge,
] containsObject:contentSize]) {
return YES;
} else {
return NO;
}
}
// Convenience for checking which UIImageSymbolScale in regards to ccessibilityLargeTextEnabled
ORK_INLINE UIImageSymbolScale ORKImageScaleToUse(void) {
if (ORKIsAccessibilityLargeTextEnabled()) {
return UIImageSymbolScaleMedium;
} else {
return UIImageSymbolScaleLarge;
}
}
// Creates a string suitable for Voice Over by joining the variables with ", " and avoiding nil and empty strings.
#define ORKAccessibilityStringForVariables(...) _ORKAccessibilityStringForVariables(ORK_NARG(__VA_ARGS__), ##__VA_ARGS__)
ORK_EXTERN NSString *_ORKAccessibilityStringForVariables(NSInteger numParameters, NSString *baseString, ...);
@@ -29,7 +29,7 @@
*/
#import <UIKit/UIKit.h>
@import UIKit;
NS_ASSUME_NONNULL_BEGIN
@@ -29,7 +29,7 @@
*/
#import <UIKit/UIKit.h>
@import UIKit;
NS_ASSUME_NONNULL_BEGIN
+283
View File
@@ -0,0 +1,283 @@
/*
Copyright (c) 2019, Novartis.
Copyright (c) 2019, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors
may be used to endorse or promote products derived from this software without
specific prior written permission. No license is granted to the trademarks of
the copyright holders even if such marks are included in this software.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public enum CircleSliderOption {
case startAngle(Double)
case barColor(UIColor)
case trackingColor(UIColor)
case thumbColor(UIColor)
case thumbImage(UIImage)
case barWidth(CGFloat)
case thumbWidth(CGFloat)
case maxValue(Float)
case minValue(Float)
case sliderEnabled(Bool)
case viewInset(CGFloat)
case minMaxSwitchTreshold(Float)
}
open class CircleSlider: UISlider {
private let minThumbTouchAreaWidth: CGFloat = 44
private var latestDegree: Double = 0
private var startValue: Float = 0
open var sliderValue: Float {
get {
return startValue
}
set {
var value = newValue
let significantChange = (maxValue - minValue) * (1.0 - minMaxSwitchTreshold)
let isSignificantChangeOccured = abs(newValue - startValue) > significantChange
if isSignificantChangeOccured {
if startValue < newValue {
value = minValue
} else {
value = maxValue
}
} else {
value = newValue
}
startValue = value
sendActions(for: .valueChanged)
var degree = Math.degreeFromValue(startAngle, value: sliderValue, maxValue: maxValue, minValue: minValue)
if startValue == maxValue {
degree -= degree / (360 * 100)
}
layout(degree)
}
}
private var trackLayer: TrackLayer! {
didSet {
layer.addSublayer(trackLayer)
}
}
private var thumbView: UIView! {
didSet {
if sliderEnabled {
thumbView.backgroundColor = thumbColor
thumbView.center = thumbCenter(startAngle)
thumbView.layer.cornerRadius = thumbView!.bounds.size.width * 0.5
addSubview(thumbView)
if let thumbImage = thumbImage {
let thumbImageView = UIImageView(frame: thumbView.bounds)
thumbImageView.image = thumbImage
thumbView.addSubview(thumbImageView)
thumbView.backgroundColor = UIColor.clear
}
} else {
thumbView.isHidden = true
}
}
}
private var startAngle: Double = -90
private var barColor = UIColor.lightGray
private var trackingColor = UIColor.blue
private var thumbColor = UIColor.black
private var barWidth: CGFloat = 20
private var maxValue: Float = 101
private var minValue: Float = 0
private var sliderEnabled = true
private var viewInset: CGFloat = 20
private var minMaxSwitchTreshold: Float = 0.0
private var thumbImage: UIImage?
private var _thumbWidth: CGFloat?
private var thumbWidth: CGFloat {
get {
if let retValue = _thumbWidth {
return retValue
}
return (thumbImage?.size.height)!
}
set {
_thumbWidth = newValue
}
}
override open func awakeFromNib() {
super.awakeFromNib()
backgroundColor = UIColor.clear
}
public init(frame: CGRect, options: [CircleSliderOption]?) {
super.init(frame: frame)
if let options = options {
build(options)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapHandle(sender:)))
tapGesture.numberOfTouchesRequired = 1
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(tapHandle(sender:)))
addGestureRecognizer(tapGesture)
addGestureRecognizer(panGesture)
}
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override open func layoutSublayers(of layer: CALayer) {
if trackLayer == nil {
trackLayer = TrackLayer(bounds: bounds.insetBy(dx: viewInset, dy: viewInset), setting: createLayerSetting())
}
if thumbView == nil {
if let image = thumbImage {
thumbView = UIView(frame: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
} else {
thumbView = UIView(frame: CGRect(x: 0, y: 0, width: thumbWidth, height: thumbWidth))
}
}
}
override open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !sliderEnabled {
return nil
}
return self
}
open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
var bounds = self.bounds
bounds = bounds.insetBy(dx: 100.0, dy: 100.0)
return bounds.contains(point)
}
override open func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let degree = Math.pointPairToBearingDegrees(center, endPoint: touch.location(in: self))
latestDegree = degree
layout(degree)
let value = Float(Math.adjustValue(startAngle, degree: degree, maxValue: maxValue, minValue: minValue))
thumbView.transform = CGAffineTransform(rotationAngle: CGFloat(Math.degreesToRadians(degree)))
sliderValue = value
return true
}
@objc
func tapHandle(sender: UIGestureRecognizer) {
if isUserInteractionEnabled {
let degree = Math.pointPairToBearingDegrees(center, endPoint: sender.location(in: self))
latestDegree = degree
layout(degree)
let value = Float(Math.adjustValue(startAngle, degree: degree, maxValue: maxValue, minValue: minValue))
thumbView.transform = CGAffineTransform(rotationAngle: CGFloat(Math.degreesToRadians(degree)))
sliderValue = value
}
}
open func changeOptions(_ options: [CircleSliderOption]) {
build(options)
redraw()
}
private func redraw() {
if trackLayer != nil {
trackLayer.removeFromSuperlayer()
}
trackLayer = TrackLayer(bounds: bounds.insetBy(dx: viewInset, dy: viewInset), setting: createLayerSetting())
if thumbView != nil {
thumbView.removeFromSuperview()
}
if let image = thumbImage {
thumbView = UIView(frame: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
} else {
thumbView = UIView(frame: CGRect(x: 0, y: 0, width: thumbWidth, height: thumbWidth))
}
self.layout(self.latestDegree)
}
func build(_ options: [CircleSliderOption]) {
for option in options {
switch option {
case let .startAngle(value):
startAngle = value
latestDegree = startAngle
case let .barColor(value):
barColor = value
case let .trackingColor(value):
trackingColor = value
case let .thumbColor(value):
thumbColor = value
case let .barWidth(value):
barWidth = value
case let .thumbWidth(value):
thumbWidth = value
case let .maxValue(value):
maxValue = value
maxValue += 1
case let .minValue(value):
minValue = value
startValue = minValue
case let .sliderEnabled(value):
sliderEnabled = value
case let .viewInset(value):
viewInset = value
case let .minMaxSwitchTreshold(value):
minMaxSwitchTreshold = value
case let .thumbImage(value):
thumbImage = value
}
}
}
private func layout(_ degree: Double) {
if let trackLayer = trackLayer, let thumbView = self.thumbView {
trackLayer.degree = degree
thumbView.center = thumbCenter(degree)
thumbView.transform = CGAffineTransform(rotationAngle: CGFloat(Math.degreesToRadians(degree)))
trackLayer.setNeedsDisplay()
}
}
private func createLayerSetting() -> TrackLayer.Setting {
var setting = TrackLayer.Setting()
setting.startAngle = startAngle
setting.barColor = barColor
setting.trackingColor = trackingColor
setting.barWidth = barWidth
return setting
}
private func thumbCenter(_ degree: Double) -> CGPoint {
let radius = (bounds.insetBy(dx: viewInset, dy: viewInset).width * 0.5) - (barWidth * 0.5) + 5
return Math.pointFromAngle(frame, angle: degree, radius: Double(radius))
}
}
@@ -0,0 +1,126 @@
/*
Copyright (c) 2019, Novartis.
Copyright (c) 2019, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors
may be used to endorse or promote products derived from this software without
specific prior written permission. No license is granted to the trademarks of
the copyright holders even if such marks are included in this software.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public enum DeviceType: String {
case iPhone5 = "iPhone5"
case iPhone5C = "iPhone5C"
case iPhone5S = "iPhone5S"
case iPhone6Plus = "iPhone6Plus"
case iPhone6 = "iPhone6"
case iPhone6S = "iPhone6S"
case iPhone6SPlus = "iPhone6SPlus"
case iPhone7 = "iPhone7"
case iPhone7Plus = "iPhone7Plus"
case iPhoneSE = "iPhoneSE"
case IPodTouch5 = "iPod5,1"
case IPodTouch6 = "iPod7,1"
}
func parseDeviceType(_ identifier: String) -> DeviceType {
switch identifier {
case "iPhone5,1", "iPhone5,2": return .iPhone5
case "iPhone5,3", "iPhone5,4": return .iPhone5C
case "iPhone6,1", "iPhone6,2": return .iPhone5S
case "iPhone7,1": return .iPhone6Plus
case "iPhone7,2": return .iPhone6
case "iPhone8,2": return .iPhone6SPlus
case "iPhone8,1": return .iPhone6S
case "iPhone9,1", "iPhone9,3": return .iPhone7
case "iPhone9,2", "iPhone9,4": return .iPhone7Plus
case "iPhone8,4": return .iPhoneSE
case "iPod5,1": return .IPodTouch5
case "iPod7,1": return .IPodTouch6
default:
if UIDevice.iPhonePlus {
return .iPhone7Plus
} else {
return .iPhone7
}
}
}
var pixelPerInchIphonePlus: CGFloat = 401
var pixelPerInchIphone: CGFloat = 326
var inchPerMm: CGFloat = 25.4
var renderedPixels: CGFloat = 1.15
func parsePixelPerInch(deviceType: DeviceType) -> CGFloat {
switch deviceType {
case .iPhone5, .iPhone5C, .iPhone5S, .iPhoneSE, .iPhone6, .iPhone6S, .iPhone7, .IPodTouch5, .IPodTouch6: return pixelPerInchIphone
case .iPhone6Plus, .iPhone6SPlus, .iPhone7Plus: return pixelPerInchIphonePlus
}
}
public extension UIDevice {
class var deviceType: DeviceType {
var systemInfo = utsname()
uname(&systemInfo)
let machine = systemInfo.machine
let mirror = Mirror(reflecting: machine)
var identifier = ""
for child in mirror.children {
if let value = child.value as? Int8, value != 0 {
identifier.append(String(UnicodeScalar(UInt8(value))))
}
}
return parseDeviceType(identifier)
}
class var pixelsPerMm: CGFloat {
return parsePixelPerInch(deviceType: UIDevice.deviceType) / inchPerMm
}
class var iPhonePlus: Bool {
if UIDevice.current.userInterfaceIdiom != .phone {
return false
}
if UIScreen.main.scale > 2.9 {
return true
}
return false
}
}
@@ -0,0 +1,181 @@
/*
Copyright (c) 2019, Novartis.
Copyright (c) 2019, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors
may be used to endorse or promote products derived from this software without
specific prior written permission. No license is granted to the trademarks of
the copyright holders even if such marks are included in this software.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
internal class EyeActivitySlider: UIView {
private var testType: VisionStepType?
private var incorrectAnswers = 0
private let contentGap: CGFloat = 20.0
private let toleranceAngle = 22.5
private let letterAngles = [0, 45, 90, 135, 180, 225, 270, 315]
private var letterSize: CGFloat {
var letterSize: CGFloat!
if self.testType == .visualAcuity {
letterSize = letterMmSizes[currentStep] * UIDevice.pixelsPerMm / UIScreen.main.nativeScale
} else {
letterSize = 20 * UIDevice.pixelsPerMm / UIScreen.main.nativeScale
}
return letterSize
}
private var currentStep = 0
private var letterMmSizes: [CGFloat] = [5.82, 4.65, 3.72, 2.91, 2.33, 1.86, 1.45, 1.16, 0.93, 0.73, 0.58, 0.47, 0.37]
private var contrastLevels: [CGFloat] = [0.9, 0.92, 0.937, 0.95, 0.96, 0.968, 0.975, 0.98, 0.984, 0.9875, 0.99]
private var stepScores: [Int] = [50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110]
private var letterAngle = 0.0
private lazy var letterImageView: UIImageView = {
let letterImage = UIImage(named: "iCNLandoltC",
in: Bundle(for: type(of: self)),
compatibleWith: nil)
let imageView = UIImageView(image: letterImage!)
return imageView
}()
private lazy var circleImageView: UIImageView = {
let circleImage = UIImage(named: "orangeGrayCircle",
in: Bundle(for: type(of: self)),
compatibleWith: nil)
return UIImageView(image: circleImage!)
}()
private var slider: CircleSlider?
internal init(testType: VisionStepType) {
super.init(frame: CGRect())
self.testType = testType
commonInit()
}
required internal init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
addSubview(letterImageView)
circleImageView.contentMode = .scaleAspectFit
addSubview(circleImageView)
let thumbImage = UIImage(named: "iCNDialPointerWithShadow",
in: Bundle(for: type(of: self)),
compatibleWith: nil)
slider = CircleSlider(frame: bounds, options: [
CircleSliderOption.barColor(UIColor.clear),
CircleSliderOption.trackingColor(UIColor.clear),
CircleSliderOption.startAngle(0),
CircleSliderOption.maxValue(360),
CircleSliderOption.minValue(0),
CircleSliderOption.thumbImage(thumbImage!)
])
addSubview(slider!)
updateSliderAndLetter()
}
override public func layoutSubviews() {
super.layoutSubviews()
letterAngle = Double(letterAngles[Int(arc4random_uniform(7))])
letterImageView.transform = CGAffineTransform.identity
letterImageView.frame = CGRect(origin: CGPoint(), size: CGSize(width: letterSize, height: letterSize))
letterImageView.center = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)
letterImageView.transform = CGAffineTransform(rotationAngle: CGFloat(Math.degreesToRadians(letterAngle)))
letterImageView.alpha = getAlpha()
slider?.frame = bounds
var frame = contentFrame()
circleImageView.frame = frame
let labelMargin: CGFloat = 30.0
frame.origin.x += labelMargin
frame.origin.y += labelMargin
frame.size.width -= labelMargin * 2
frame.size.height -= labelMargin * 2
}
private func updateSliderAndLetter() {
guard incorrectAnswers < 2 else { return }
letterImageView.isHidden = false
slider?.sliderValue = 0
slider?.isUserInteractionEnabled = true
slider?.isHidden = false
setNeedsLayout()
}
private func contentFrame() -> CGRect {
let sideLength = min(bounds.size.width, bounds.size.height) - contentGap
let contentFrame = CGRect(x: (bounds.size.width - sideLength) / 2, y: (bounds.size.height - sideLength) / 2, width: sideLength, height: sideLength)
return contentFrame
}
private func getAlpha() -> CGFloat {
return testType == .visualAcuity ? 1.0 : (1 - contrastLevels[currentStep])
}
private func getResult() -> Bool {
let sliderValue = Double((slider?.sliderValue)!)
let leftMargin = letterAngle - toleranceAngle
let rightMargin = letterAngle + toleranceAngle
let result = sliderValue > leftMargin && sliderValue < rightMargin
if result == false {
incorrectAnswers += 1
} else {
currentStep += 1
}
return result
}
internal func hideLetter() {
letterImageView.isHidden = true
}
internal func fetchResultDataAndUpdateSlider() -> (outcome: Bool, letterAngle: Double, sliderAngle: Double, score: Int, incorrectAnswers: Int, maxScore: Int) {
let outcome = getResult()
let score = stepScores[currentStep]
let currentSliderValue = Double((slider?.sliderValue)!)
let currentLetterAngle = letterAngle
let maxScore = testType == .visualAcuity ? stepScores.last! : stepScores[contrastLevels.count - 1]
updateSliderAndLetter()
return (outcome, currentLetterAngle, currentSliderValue, score, incorrectAnswers, maxScore)
}
}
+76
View File
@@ -0,0 +1,76 @@
/*
Copyright (c) 2019, Novartis.
Copyright (c) 2019, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors
may be used to endorse or promote products derived from this software without
specific prior written permission. No license is granted to the trademarks of
the copyright holders even if such marks are included in this software.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
internal class Math {
internal class func degreesToRadians(_ angle: Double) -> Double {
return angle / 180 * .pi
}
internal class func pointFromAngle(_ frame: CGRect, angle: Double, radius: Double) -> CGPoint {
let radian = degreesToRadians(angle)
let xPoint = Double(frame.midX) + cos(radian) * radius
let yPoint = Double(frame.midY) + sin(radian) * radius
return CGPoint(x: xPoint, y: yPoint)
}
internal class func pointPairToBearingDegrees(_ startPoint: CGPoint, endPoint: CGPoint) -> Double {
let originPoint = CGPoint(x: endPoint.x - startPoint.x, y: endPoint.y - startPoint.y)
let bearingRadians = atan2(Double(originPoint.y), Double(originPoint.x))
var bearingDegrees = bearingRadians * (180.0 / .pi)
bearingDegrees = (bearingDegrees > 0.0 ? bearingDegrees : (360.0 + bearingDegrees))
return bearingDegrees
}
internal class func adjustValue(_ startAngle: Double, degree: Double, maxValue: Float, minValue: Float) -> Double {
let ratio = Double((maxValue - minValue) / 360)
let ratioStart = ratio * startAngle
let ratioDegree = ratio * degree
let adjustValue: Double
if startAngle < 0 {
adjustValue = (360 + startAngle) > degree ? (ratioDegree - ratioStart) : (ratioDegree - ratioStart) - (360 * ratio)
} else {
adjustValue = (360 - (360 - startAngle)) < degree ? (ratioDegree - ratioStart) : (ratioDegree - ratioStart) + (360 * ratio)
}
return adjustValue + (Double(minValue))
}
internal class func adjustDegree(_ startAngle: Double, degree: Double) -> Double {
return (360 + startAngle) > degree ? degree : -(360 - degree)
}
internal class func degreeFromValue(_ startAngle: Double, value: Float, maxValue: Float, minValue: Float) -> Double {
let ratio = Double((maxValue - minValue) / 360)
let angle = Double(value) / ratio
return angle + startAngle - (Double(minValue) / ratio)
}
}
@@ -28,12 +28,13 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <ResearchKitActiveTask/ORK3DModelManager.h>
#import <ResearchKit/ORK3DModelManager.h>
#import <ResearchKit/ORKActiveStep.h>
#import <ResearchKit/ORKDefines.h>
NS_ASSUME_NONNULL_BEGIN
ORK_CLASS_AVAILABLE
@interface ORK3DModelStep : ORKActiveStep
@@ -29,10 +29,15 @@
*/
#import "ORK3DModelStep.h"
#import "ORK3DModelStepViewController.h"
#import "ORKHelpers_Internal.h"
@implementation ORK3DModelStep
+ (Class)stepViewControllerClass {
return [ORK3DModelStepViewController class];
}
- (instancetype)initWithIdentifier:(NSString *)identifier modelManager:(nonnull ORK3DModelManager *)modelManager {
self = [super initWithIdentifier:identifier];
@@ -85,8 +90,7 @@
return (isParentSame && ORKEqualObjects(self.modelManager, castObject.modelManager));
}
- (NSUInteger)hash
{
- (NSUInteger)hash {
return [super hash] ^ [_modelManager hash];
}
@@ -28,8 +28,8 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/Foundation.h>
#import <ResearchKitActiveTask/ORKActiveStepViewController.h>
@import Foundation;
#import <ResearchKit/ORKActiveStepViewController.h>
#import <ResearchKit/ORKDefines.h>
NS_ASSUME_NONNULL_BEGIN
@@ -115,11 +115,11 @@
[self.motionManager startAccelerometerUpdatesToQueue:[[NSOperationQueue alloc] init] withHandler:^(CMAccelerometerData *data, NSError *error) {
BOOL success = NO;
if (data) {
success = [self->_logger append:[data ork_JSONDictionary] error:&error];
success = [_logger append:[data ork_JSONDictionary] error:&error];
}
if (!success) {
dispatch_async(dispatch_get_main_queue(), ^{
self->_recordingError = error;
_recordingError = error;
[self stop];
});
}
@@ -28,7 +28,7 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <ResearchKitActiveTask/ORKStroopResult.h>
#import <ResearchKit/ORKStroopResult.h>
NS_ASSUME_NONNULL_BEGIN
@@ -29,10 +29,15 @@
*/
#import "ORKAccuracyStroopStep.h"
#import "ORKAccuracyStroopStepViewController.h"
#import "ORKHelpers_Internal.h"
@implementation ORKAccuracyStroopStep
+ (Class)stepViewControllerClass {
return ORKAccuracyStroopStepViewController.class;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@@ -28,9 +28,9 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/Foundation.h>
@import Foundation;
#import <ResearchKit/ORKDefines.h>
#import <ResearchKitUI/ORKStepViewController.h>
#import <ResearchKit/ORKStepViewController.h>
NS_ASSUME_NONNULL_BEGIN
@@ -29,8 +29,8 @@
*/
#import <UIKit/UIKit.h>
#import <HealthKit/HealthKit.h>
@import UIKit;
@import HealthKit;
#import <ResearchKit/ORKStep.h>
@@ -39,10 +39,9 @@
NS_ASSUME_NONNULL_BEGIN
/**
A base class for steps in an active task.
Active Steps collect sensor data in a semi-controlled environment, as opposed
to the passive data collection enabled by HealthKit, or the more subjective data
The `ORKActiveStep` class is the base class for steps in active tasks, which
are steps that collect sensor data in a semi-controlled environment, as opposed
to the purely passive data collection enabled by HealthKit, or the more subjective data
collected when users fill in surveys.
In addition to the behaviors of `ORKStep`, active steps have the concept of
@@ -32,6 +32,7 @@
#import "ORKActiveStep.h"
#import "ORKActiveStep_Internal.h"
#import "ORKActiveStepViewController.h"
#import "ORKRecorder_Private.h"
#import "ORKStep_Private.h"
@@ -41,12 +42,12 @@
@implementation ORKActiveStep
- (BOOL)startsFinished {
return (_stepDuration == 0);
+ (Class)stepViewControllerClass {
return [ORKActiveStepViewController class];
}
- (BOOL)hasAudioRecording {
return NO;
- (BOOL)startsFinished {
return (_stepDuration == 0);
}
- (BOOL)hasCountDown {
@@ -98,8 +99,8 @@
step.shouldVibrateOnFinish = self.shouldVibrateOnFinish;
step.shouldUseNextAsSkipButton = self.shouldUseNextAsSkipButton;
step.shouldContinueOnFinish = self.shouldContinueOnFinish;
step.spokenInstruction = [self.spokenInstruction copy];
step.finishedSpokenInstruction = [self.finishedSpokenInstruction copy];
step.spokenInstruction = self.spokenInstruction;
step.finishedSpokenInstruction = self.finishedSpokenInstruction;
step.recorderConfigurations = [self.recorderConfigurations copy];
return step;
}
@@ -30,7 +30,7 @@
@import UIKit;
#import <ResearchKitUI/ORKLabel.h>
#import <ResearchKit/ORKLabel.h>
NS_ASSUME_NONNULL_BEGIN
@@ -131,7 +131,7 @@ static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
}
dispatch_async(dispatch_get_main_queue(), ^{
self->_handler(self, finished);
_handler(self, finished);
});
}
@@ -29,8 +29,8 @@
*/
#import <UIKit/UIKit.h>
#import <ResearchKitUI/ORKStepViewController.h>
@import UIKit;
#import <ResearchKit/ORKStepViewController.h>
#import <ResearchKit/ORKRecorder.h>
@@ -57,7 +57,6 @@
#import "ORKHelpers_Internal.h"
#import "ORKSkin.h"
NSString * const ORKActiveStepViewAccessibilityIdentifier = @"ORKActiveStepView";
@interface ORKActiveStepViewController () {
ORKActiveStepView *_activeStepView;
@@ -335,7 +334,6 @@ NSString * const ORKActiveStepViewAccessibilityIdentifier = @"ORKActiveStepView"
[self resetTimer];
[self prepareRecorders];
_activeStepView.accessibilityIdentifier = ORKActiveStepViewAccessibilityIdentifier;
}
- (void)startRecorders {
@@ -573,7 +571,7 @@ static NSString *const _ORKRecorderResultsRestoreKey = @"recorderResults";
[super decodeRestorableStateWithCoder:coder];
self.finished = [coder decodeBoolForKey:_ORKFinishedRestoreKey];
_recorderResults = [coder decodeObjectOfClass:[NSArray class] forKey:_ORKRecorderResultsRestoreKey];
_recorderResults = [coder decodeObjectOfClasses:[NSSet setWithArray:@[NSArray.self, ORKResult.self]] forKey:_ORKRecorderResultsRestoreKey];
}
@end
@@ -29,7 +29,7 @@
*/
#import <ResearchKitActiveTask/ORKActiveStepViewController.h>
#import <ResearchKit/ORKActiveStepViewController.h>
NS_ASSUME_NONNULL_BEGIN
@@ -42,7 +42,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)hasTitle;
- (BOOL)hasText;
- (BOOL)hasVoice;
- (BOOL)hasAudioRecording;
@end
NS_ASSUME_NONNULL_END
@@ -31,6 +31,7 @@
#import "ORKAmslerGridStep.h"
#import "ORKHelpers_Internal.h"
#import "ORKAmslerGridStepViewController.h"
@implementation ORKAmslerGridStep
@@ -46,6 +47,10 @@
return self;
}
+ (Class)stepViewControllerClass {
return [ORKAmslerGridStepViewController class];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
@@ -30,7 +30,7 @@
#import <UIKit/UIKit.h>
#import <ResearchKit/ORKDefines.h>
#import <ResearchKitActiveTask/ORKActiveStepViewController.h>
#import <ResearchKit/ORKActiveStepViewController.h>
NS_ASSUME_NONNULL_BEGIN
@@ -68,3 +68,4 @@ typedef void (^ORKAudioStepContentViewEventHandler)(ORKAudioContentViewEvent);
@end
NS_ASSUME_NONNULL_END
@@ -29,7 +29,7 @@
*/
#import <ResearchKit/ResearchKit.h>
#import <ResearchKitActiveTask/ORKFitnessStep.h>
#import <ResearchKit/ORKFitnessStep.h>
NS_ASSUME_NONNULL_BEGIN
@@ -29,6 +29,7 @@
*/
#import "ORKAudioFitnessStep.h"
#import "ORKAudioFitnessStepViewController.h"
#import "ORKBundleAsset.h"
#import "ORKHelpers_Internal.h"
@@ -39,7 +40,7 @@
self = [super init];
if (self) {
self.time = time;
self.spokenText = spokenText;
self.spokenText = [spokenText copy];
}
return self;
}
@@ -82,6 +83,10 @@
@implementation ORKAudioFitnessStep
- (Class)stepViewControllerClass {
return [ORKAudioFitnessStepViewController class];
}
- (instancetype)initWithIdentifier:(NSString *)identifier
audioAsset:(ORKBundleAsset *)audioAsset
vocalCues:(nullable NSArray<ORKVocalCue *> *)vocalCues {
@@ -89,8 +94,8 @@
if (self) {
self.stepDuration = 180;
self.shouldShowDefaultTimer = NO;
self.audioAsset = audioAsset;
self.vocalCues = vocalCues == nil ? [NSArray new] : vocalCues;
self.audioAsset = [audioAsset copy];
self.vocalCues = vocalCues == nil ? [NSArray new] : [vocalCues copy];
}
return self;
}
@@ -29,7 +29,7 @@
*/
#import <ResearchKit/ResearchKit.h>
#import <ResearchKitActiveTask/ORKFitnessStepViewController.h>
#import <ResearchKit/ORKFitnessStepViewController.h>
NS_ASSUME_NONNULL_BEGIN
@@ -30,7 +30,7 @@
@import UIKit;
#import "ORKAudioMeteringView.h"
#import <ResearchKit/ORKAudioMeteringView.h>
NS_ASSUME_NONNULL_BEGIN
@@ -54,7 +54,6 @@ extern NSArray<NSNumber *> * ORKLastNSamples(NSArray<NSNumber *> *samples, NSInt
@interface ORKAudioMeteringView : UIView <ORKAudioMetering, ORKAudioMeteringDisplay>
@end
NS_ASSUME_NONNULL_END
@@ -29,6 +29,8 @@
*/
#import "ORKAudioMeteringView.h"
#import "ORKAudioGraphView.h"
NSArray<NSNumber *> * ORKLastNSamples(NSArray<NSNumber *> *samples, NSInteger limit) {
@@ -41,83 +43,96 @@ NSArray<NSNumber *> * ORKLastNSamples(NSArray<NSNumber *> *samples, NSInteger li
return [samples copy];
}
@interface ORKAudioMeteringView ()
@property (nonatomic, strong) UIView<ORKAudioMetering, ORKAudioMeteringDisplay> *meteringView;
@end
@implementation ORKAudioMeteringView
- (instancetype)init {
- (instancetype)init
{
self = [super init];
if (self) {
if (self)
{
[self configureMeteringView];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
if (self)
{
[self configureMeteringView];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
if (self)
{
[self configureMeteringView];
}
return self;
}
- (void)configureMeteringView {
- (void)configureMeteringView
{
if (!_meteringView) {
[self setMeteringView:[[ORKAudioGraphView alloc] init]];
}
}
- (void)layoutSubviews {
- (void)layoutSubviews
{
[super layoutSubviews];
[_meteringView setFrame:[self bounds]];
}
- (void)setHidden:(BOOL)hidden {
- (void)setHidden:(BOOL)hidden
{
[super setHidden:hidden];
[_meteringView setHidden:hidden];
}
- (void)didMoveToSuperview {
- (void)didMoveToSuperview
{
[super didMoveToSuperview];
if ([self superview] == nil) {
if ([self superview] == nil)
{
[_meteringView removeFromSuperview];
}
else {
else
{
[self addSubview:_meteringView];
}
}
#pragma mark - ORKAudioMetering
- (void)setSamples:(NSArray<NSNumber *> *)samples {
- (void)setSamples:(NSArray<NSNumber *> *)samples
{
[_meteringView setSamples:samples];
}
- (void)setAlertThreshold:(float)threshold {
- (void)setAlertThreshold:(float)threshold
{
[_meteringView setAlertThreshold:threshold];
}
#pragma mark - ORKAudioMeteringDisplay
- (void)setMeterColor:(UIColor *)meterColor {
- (void)setMeterColor:(UIColor *)meterColor
{
[_meteringView setMeterColor:meterColor];
}
- (void)setAlertColor:(UIColor *)alertColor {
- (void)setAlertColor:(UIColor *)alertColor
{
[_meteringView setAlertColor:alertColor];
}
@@ -31,6 +31,8 @@
#import "ORKAudioStep.h"
#import "ORKAudioStepViewController.h"
#import "ORKStep_Private.h"
#import "ORKHelpers_Internal.h"
@@ -38,6 +40,10 @@
@implementation ORKAudioStep
+ (Class)stepViewControllerClass {
return [ORKAudioStepViewController class];
}
- (instancetype)initWithIdentifier:(NSString *)identifier {
self = [super initWithIdentifier:identifier];
if (self) {
@@ -30,9 +30,9 @@
#import <Foundation/Foundation.h>
@import Foundation;
#import <ResearchKit/ORKDefines.h>
#import <ResearchKitActiveTask/ORKActiveStepViewController.h>
#import <ResearchKit/ORKActiveStepViewController.h>
NS_ASSUME_NONNULL_BEGIN
@@ -39,29 +39,16 @@
- (instancetype)initWithIdentifier:(NSString *)identifier {
self = [super initWithIdentifier:identifier];
if (self != nil) {
_bypassAudioEngineStart = NO;
}
return self;
}
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
if (_bypassAudioEngineStart) {
return nil;
}
ORKAudioStreamer *obj = [[ORKAudioStreamer alloc] initWithIdentifier:self.identifier step:step];
return obj;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
_bypassAudioEngineStart = NO;
return self;
}
@@ -31,8 +31,15 @@
#import "ORKCountdownStep.h"
#import "ORKCountdownStepViewController.h"
@implementation ORKCountdownStep
+ (Class)stepViewControllerClass {
return [ORKCountdownStepViewController class];
}
- (instancetype)initWithIdentifier:(NSString *)identifier {
self = [super initWithIdentifier:identifier];
if (self) {
@@ -29,9 +29,9 @@
*/
#import <Foundation/Foundation.h>
@import Foundation;
#import <ResearchKit/ORKDefines.h>
#import <ResearchKitActiveTask/ORKActiveStepViewController.h>
#import <ResearchKit/ORKActiveStepViewController.h>
NS_ASSUME_NONNULL_BEGIN

Some files were not shown because too many files have changed in this diff Show More