Compare commits

...

8 Commits

Author SHA1 Message Date
Pariece McKinney c71d1a56dd Public release 3.0.1 2024-05-08 14:56:11 -04:00
Corey 6c24a7e922 ci: Add code integration testing to repo (#1569) 2024-04-10 14:33:32 -04:00
Pariece McKinney b14e5cfcb0 Public Release 3.0 2024-03-28 19:39:04 -04:00
Louie fee76c2d93 Merge branch 'stable' into main 2023-12-05 14:00:17 -08:00
Louie a07041901b Public Release 2.2.15
Various ORKTextChoiceOther and ORKChoiceOtherViewCell improvements
Bumped version to 2.2.15
Bumped Cocoapod version to 2.2.15
2023-12-05 11:19:31 -08:00
Louie Chatta c370011616 Public Release 2.2.15
- Various ORKTextChoiceOther and ORKChoiceOtherViewCell improvements 
- Bumped version to 2.2.15
- Bumped Cocoapod version to 2.2.15
2023-12-05 11:02:55 -08:00
Louie 1b9daf60ee Merge pull request #1530 from ResearchKit/cocoapod_version_bump
Point ResearchKit.podspec to 2.1.0
2022-11-29 13:11:01 -08:00
Louis Chatta e71d71cf9c Point ResearchKit.podspec to 2.1.0 2022-11-29 11:48:27 -08:00
1999 changed files with 18961 additions and 163374 deletions
+26
View File
@@ -0,0 +1,26 @@
name: Build
on:
push:
branches: [ 'main', 'stable' ]
pull_request:
branches: [ 'main', 'stable' ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: macos-14
strategy:
matrix:
destination: ['platform=iOS\ Simulator,OS=17.4,name=iPhone\ 15\ Pro']
scheme: ['ResearchKit']
name: ${{ matrix.scheme }} Unit Tests
steps:
- uses: actions/checkout@v4
- name: Set Xcode Version
run: sudo xcode-select -s /Applications/Xcode_15.3.app
- name: Test
run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -verbose -workspace RKWorkspace.xcworkspace -scheme ${{ matrix.scheme }} -destination ${{ matrix.destination }} build test | xcpretty
+16 -19
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://forums.developer.apple.com/community/researchkit) or [contact us](https://developer.apple.com/contact/researchkit/) for guidance.
on the [*ResearchKit* Forum](https://developer.apple.com/forums/tags/researchkit) or [contact us](mailto:researchkit@apple.com) for guidance.
Contributing software
@@ -16,8 +16,7 @@ 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:
@@ -27,7 +26,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 master.
7. Once accepted, your pull request will be merged into main.
Choosing an issue to work on<a name="create"></a>
----------------------------
@@ -36,13 +35,12 @@ 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:
* Faster 'get started' to a useful app
* More active tasks
* Data analysis for active tasks
* More consent sections
* Back end integrations
* Improving the APIs needed to get started with a ResearchKit project
* New Active Tasks
* Data analysis for Active Tasks
* Backend integrations
If in doubt, bring your idea up on the [*ResearchKit* Forum](https://forums.developer.apple.com/community/researchkit).
If in doubt, bring your idea up on the [ResearchKit Forum](https://developer.apple.com/forums/tags/researchkit).
Creating a personal fork<a name="fork"></a>
@@ -56,8 +54,7 @@ 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
@@ -78,7 +75,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 Voice Over (or other relevant assistive technologies)
audit your components with VoiceOver (or other relevant assistive technologies)
enabled.
Keep changes that fix different issues separate. For bug fixes,
@@ -99,11 +96,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 4S, iPhone 5, iPhone 6, iPhone 6 Plus).
* Multiple device form factors (for instance, iPhone SE, iPhone 14, iPhone 15 Pro, iPhone 15 Pro Max).
* Dynamic text, especially at the "Large" setting.
* Rotation between portrait and landscape, where appropriate.
You can use the apps in the `Testing` and `samples` directories to
You can use the `ORKCatalog` app in the `samples` directory to
test your changes.
Submit a pull request<a name="request"></a>
@@ -120,7 +117,7 @@ After acceptance<a name="after"></a>
----------------
Once your pull request has been accepted, your changes will be merged
to master. You are still responsible for your change after it is
to main. You are still responsible for your change after it is
accepted. Stay in contact, in case bugs are detected that may require
your attention.
@@ -134,7 +131,7 @@ documentation, or other issues during this process.
Release process
-----------------
The `master` branch is used for work in progress. On `master`:
The `main` branch is used for work in progress. On `main`:
* All test apps should build and run error free.
* Unit tests should all pass.
@@ -142,9 +139,9 @@ The `master` branch is used for work in progress. On `master`:
base language).
The project will make periodic releases. When preparing a stable release, we
will branch from `master` to a convergence branch. During this process,
will branch from `main` to a convergence branch. During this process,
changes will be made first to the convergence branch, and then merged into
`master`. On the convergence branch, changes will be made only to:
`main`. 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, Apple Inc. All rights reserved.
Copyright (c) 2015-2024, 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:
+137 -260
View File
@@ -6,293 +6,170 @@ 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)
* [Documentation](docs/)
* [Best Practices](../../wiki/best-practices)
* [Contributing to ResearchKit](CONTRIBUTING.md)
* [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>
* [Website](https://www.researchandcare.org)
* [ResearchKit BSD License](#license)
* [WWDC: ResearchKit and CareKit Reimagined](https://developer.apple.com/videos/play/wwdc2019/217/)
Getting More Information
========================
* Join the [*ResearchKit* Forum](https://developer.apple.com/forums/tags/researchkit) for discussing uses of the *ResearchKit framework and* related projects.
### Install as an embedded framework <a name="installation"></a>
Use Cases
===========
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.
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.
<img width="1000" alt="ebedded-framework" src="https://github.com/ResearchKit/ResearchKit/assets/29615893/7479f313-ecc7-4d94-8c64-c58ae7362a4d">
Surveys
-------
### 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>
The *ResearchKit framework* provides a pre-built user interface for surveys, which can be presented
modally on an *iPhone*, *iPod Touch*, or *iPad*. See
*[Creating Surveys](docs/Survey/)* for more
information.
modally on an *iPhone* or *iPad*. The example below shows the process to present a height question for a participant to answer.
```swift
import ResearchKit
import ResearchKitUI
let sectionHeaderFormItem = ORKFormItem(sectionTitle: "Your question here.")
Consent
----------------
let heightQuestionFormItem = ORKFormItem(identifier: "heightQuestionFormItem1", text: nil, answerFormat: ORKAnswerFormat.heightAnswerFormat())
heightQuestionFormItem.placeholder = "Tap here"
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.
let formStep = ORKFormStep(identifier: "HeightQuestionIdentifier", title: "Height", text: "Local system")
formStep.formItems = [sectionHeaderFormItem, heightQuestionFormItem]
return formStep
```
Active Tasks
------------
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>
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. See
*[Active Tasks](docs/ActiveTasks/)* for more
information.
while *iPhone* sensors actively collect data.
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.
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*
Use predefined tasks provided by *ResearchKit* to guide your participants through specific actions.
```swift
let myStep = ORKInstructionStep(identifier: "intro")
myStep.title = "Welcome to ResearchKit"
```
import ResearchKit
import ResearchKitUI
import ResearchKitActiveTask
### 3. Create a Task
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`.
*Objective-C*
```objc
ORKOrderedTask *task =
[[ORKOrderedTask alloc] initWithIdentifier:@"task" steps:@[myStep]];
```
*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)
let orderedTask = ORKOrderedTask.dBHLToneAudiometryTask(withIdentifier: "dBHLToneAudiometryTaskIdentifier",
intendedUseDescription: nil, options: [])
let taskViewController = ORKTaskViewController(task: orderedTask, taskRun: nil)
taskViewController.delegate = self
present(taskViewController, animated: true, completion: nil)
present(taskViewController, animated: true)
```
The dBHL Tone Audiometry task is presented in the figure below.
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:
| | |
|---|---|
| ![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) |
*Objective-C*
# Getting Help <a name="getting-help"></a>
```objc
- (void)taskViewController:(ORKTaskViewController *)taskViewController
didFinishWithReason:(ORKTaskViewControllerFinishReason)reason
error:(NSError *)error {
GitHub is our primary forum for ResearchKit. Feel free to open up issues about questions, problems, or ideas.
ORKTaskResult *taskResult = [taskViewController result];
// You could do something with the result here.
# License <a name="license"></a>
// 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.
```
This project is made available under the terms of a BSD license. See the [LICENSE](LICENSE) file.
+41
View File
@@ -1,5 +1,46 @@
# ResearchKit Release Notes
## ResearchKit 3.0.1 Release Notes
In addition to general stability and performance improvements, ResearchKit 3.0.1 includes the following updates:
- **ORKFormStep**
The `ORKFormStep` has an additional property named `autoScrollEnabled` that allows developers to disable autoscrolling.
- **ORKBodyItem**
The `ORKBodyItem` has an additional property named `alignImageToTop` that will align a body item's image and text to the top of each other.
- **ORK3DModelStep**
An example of the `ORK3DModelStep` has been added to the ORKCatalog app.
## 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,9 +4,6 @@
<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
@@ -1,18 +0,0 @@
Pod::Spec.new do |s|
s.name = 'ResearchKit'
s.version = '2.1.0'
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 = "1310"
LastUpgradeVersion = "1500"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "NO">
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
@@ -20,20 +20,6 @@
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
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1310"
LastUpgradeVersion = "1500"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -50,6 +50,11 @@
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 = "1310"
LastUpgradeVersion = "1340"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -14,9 +14,9 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B18FF3A41A9FE25700C0C3B0"
BuildableName = "docs"
BlueprintName = "docs"
BlueprintIdentifier = "CA1C7A40288B0C68004DAB3A"
BuildableName = "ResearchKitUI.framework"
BlueprintName = "ResearchKitUI (iOS)"
ReferencedContainer = "container:ResearchKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@@ -40,15 +40,6 @@
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"
@@ -59,9 +50,9 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B18FF3A41A9FE25700C0C3B0"
BuildableName = "docs"
BlueprintName = "docs"
BlueprintIdentifier = "CA1C7A40288B0C68004DAB3A"
BuildableName = "ResearchKitUI.framework"
BlueprintName = "ResearchKitUI (iOS)"
ReferencedContainer = "container:ResearchKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
+3
View File
@@ -13,6 +13,9 @@
},
"testTargets" : [
{
"skippedTests" : [
"ORKDataCollectionTests"
],
"target" : {
"containerPath" : "container:ResearchKit.xcodeproj",
"identifier" : "86CC8E991AC09332001CCD89",
-283
View File
@@ -1,283 +0,0 @@
/*
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))
}
}
@@ -1,126 +0,0 @@
/*
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
}
}
@@ -1,181 +0,0 @@
/*
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
@@ -1,76 +0,0 @@
/*
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)
}
}
@@ -1,110 +0,0 @@
/*
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.
*/
import ResearchKit.Private
public class ORKLandoltCResult: ORKResult {
public var outcome: Bool?
public var letterAngle: Double?
public var sliderAngle: Double?
public var score: Int?
enum Keys: String {
case outcome
case letterAngle
case sliderAngle
case score
}
public init(identifier: String, outcome: Bool, letterAngle: Double, sliderAngle: Double, score: Int) {
super.init(identifier: identifier)
self.outcome = outcome
self.letterAngle = letterAngle
self.sliderAngle = sliderAngle
self.score = score
}
override public func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
aCoder.encode(outcome, forKey: Keys.outcome.rawValue)
aCoder.encode(letterAngle, forKey: Keys.letterAngle.rawValue)
aCoder.encode(sliderAngle, forKey: Keys.sliderAngle.rawValue)
aCoder.encode(score, forKey: Keys.score.rawValue)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
outcome = aDecoder.decodeObject(forKey: Keys.outcome.rawValue) as? Bool ?? false
letterAngle = aDecoder.decodeObject(forKey: Keys.letterAngle.rawValue) as? Double ?? 0.0
sliderAngle = aDecoder.decodeObject(forKey: Keys.sliderAngle.rawValue) as? Double ?? 0.0
score = aDecoder.decodeObject(forKey: Keys.score.rawValue) as? Int ?? 0
}
override public func copy(with zone: NSZone? = nil) -> Any {
let result = super.copy(with: zone) as! ORKLandoltCResult
result.outcome = outcome
result.letterAngle = letterAngle
result.sliderAngle = sliderAngle
result.score = score
return result
}
override public func isEqual(_ object: Any?) -> Bool {
let isParentSame = super.isEqual(object)
if let castObject = object as? ORKLandoltCResult {
return (isParentSame &&
ORKEqualObjects(outcome as Any, castObject.outcome as Any) &&
ORKEqualObjects(letterAngle as Any, castObject.letterAngle as Any) &&
ORKEqualObjects(sliderAngle as Any, castObject.sliderAngle as Any) &&
ORKEqualObjects(score as Any, castObject.score as Any))
}
return true
}
override public func description(withNumberOfPaddingSpaces numberOfPaddingSpaces: UInt) -> String {
let descriptionString = """
\(descriptionPrefix(withNumberOfPaddingSpaces: numberOfPaddingSpaces));
Outcome: \(String(describing: outcome)); LetterAngle: \(String(describing: letterAngle));
SliderAngle: \(String(describing: sliderAngle)); Score: \(String(describing: score))
"""
return descriptionString
}
}
@@ -1,105 +0,0 @@
/*
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.
*/
@objc
public enum VisionStepLeftOrRightEye: Int {
case left
case right
}
@objc
public enum VisionStepType: Int {
case visualAcuity
case contrastSensitivity
}
@objc
public class ORKLandoltCStep: ORKActiveStep {
public var testType: VisionStepType?
public var eyeToTest: VisionStepLeftOrRightEye?
enum Key: String {
case testType
case eyeToTest
}
public override class func stepViewControllerClass() -> AnyClass {
return ORKLandoltCStepViewController.self
}
public class func supportsSecureCoding() -> Bool {
return true
}
@objc
public init(identifier: String, testType: VisionStepType, eyeToTest: VisionStepLeftOrRightEye) {
super.init(identifier: identifier)
self.testType = testType
self.eyeToTest = eyeToTest
}
public override var allowsBackNavigation: Bool {
return false
}
public override func copy(with zone: NSZone? = nil) -> Any {
let visionStep: ORKLandoltCStep = super.copy(with: zone) as! ORKLandoltCStep
return visionStep
}
public required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if let typeValue = aDecoder.decodeObject(forKey: "stepType") as? Int {
testType = VisionStepType(rawValue: typeValue)
}
if let eyeValue = aDecoder.decodeObject(forKey: "eyeToTest") as? Int {
eyeToTest = VisionStepLeftOrRightEye(rawValue: eyeValue)
}
}
public override func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
aCoder.encode(testType, forKey: Key.testType.rawValue)
aCoder.encode(eyeToTest, forKey: Key.eyeToTest.rawValue)
}
public override func isEqual(_ object: Any?) -> Bool {
if let object = object as? ORKLandoltCStep {
return testType == object.testType && eyeToTest == object.eyeToTest
}
return false
}
}
@@ -1,81 +0,0 @@
/*
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.
*/
import ResearchKit.Private
internal class ORKLandoltCStepContentView: UIView {
var eyeActivitySlider: EyeActivitySlider?
private var testType: VisionStepType?
internal init(testType: VisionStepType) {
super.init(frame: CGRect())
translatesAutoresizingMaskIntoConstraints = false
self.testType = testType
setupSubviews()
setupConstraints()
}
internal required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
internal func setupSubviews() {
guard let typeValue = testType else {
return
}
eyeActivitySlider = EyeActivitySlider(testType: typeValue)
addSubview(eyeActivitySlider!)
}
internal func setupConstraints() {
eyeActivitySlider?.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
NSLayoutConstraint(item: eyeActivitySlider!,
attribute: .width,
relatedBy: .equal,
toItem: self,
attribute: .width,
multiplier: 1.0,
constant: 0.0),
NSLayoutConstraint(item: eyeActivitySlider!,
attribute: .height,
relatedBy: .equal,
toItem: self,
attribute: .width,
multiplier: 1.0,
constant: 0.0)
])
}
}
@@ -1,268 +0,0 @@
/*
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.
*/
import ResearchKit.Private
public class ORKLandoltCStepViewController: ORKActiveStepViewController {
private var activityTimer = Timer()
private var results = NSMutableArray()
private var visionStepView: ORKLandoltCStepView
private var eyeToTest: VisionStepLeftOrRightEye?
private var testType: VisionStepType?
public override init(step: ORKStep?) {
if let visionStep = step as? ORKLandoltCStep {
eyeToTest = visionStep.eyeToTest
testType = visionStep.testType
}
visionStepView = ORKLandoltCStepView(testType: testType)
super.init(step: step)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override var result: ORKStepResult? {
let stepResult = super.result
stepResult?.results = results.copy() as? [ORKResult]
return stepResult!
}
public override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
activeStepView?.customContentView = visionStepView
activeStepView?.removeCustomContentPadding()
activeStepView?.customContentFillsAvailableSpace = true
// TODO: Localize
visionStepView.currentEyeLabel.text = eyeToTest == .left ? "Left Eye" : "Right Eye"
visionStepView.continueButton.addTarget(self, action: #selector(continueButtonWasPressed), for: .touchUpInside)
startTimer()
}
override public func stepDidFinish() {
super.stepDidFinish()
goForward()
}
private func startTimer() {
activityTimer = Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(hideCircle), userInfo: nil, repeats: false)
}
@objc
private func hideCircle() {
activityTimer.invalidate()
visionStepView.visionContentView?.eyeActivitySlider?.hideLetter()
visionStepView.topInstructionLabel.isHidden = false
}
@objc
private func continueButtonWasPressed() {
activityTimer.invalidate()
visionStepView.topInstructionLabel.isHidden = true
visionStepView.continueButton.isEnabled = false
if let resultData = visionStepView.visionContentView?.eyeActivitySlider?.fetchResultDataAndUpdateSlider() {
let stepResult: ORKLandoltCResult = ORKLandoltCResult(identifier: step!.identifier,
outcome: resultData.outcome,
letterAngle: resultData.letterAngle,
sliderAngle: resultData.sliderAngle,
score: resultData.score)
results.add(stepResult)
if resultData.incorrectAnswers == 2 || resultData.score == resultData.maxScore {
stepDidFinish()
} else {
visionStepView.continueButton.isEnabled = true
startTimer()
}
}
}
}
public class ORKLandoltCStepView: UIView {
var visionContentView: ORKLandoltCStepContentView?
let continueButtonCornerRadius: CGFloat = 12.0
let eyeLabelTopPadding: CGFloat = 20.0
let instructionLabelTopPadding: CGFloat = 15.0
let visionContentTopPadding: CGFloat = 10.0
let continueButton = ORKRoundTappingButton()
let currentEyeLabel = UILabel()
let topInstructionLabel = UILabel()
init(testType: VisionStepType!) {
super.init(frame: .zero)
setupCurrentEyeLabel()
setupTopInstructionLabel()
setupVisionContentView(testType: testType)
setupContinueButton()
setupConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupVisionContentView(testType: VisionStepType!) {
if visionContentView == nil {
visionContentView = ORKLandoltCStepContentView(testType: testType)
}
addSubview(visionContentView!)
}
func setupCurrentEyeLabel() {
currentEyeLabel.isHidden = true
currentEyeLabel.textAlignment = .center
currentEyeLabel.textColor = UIColor.black
currentEyeLabel.numberOfLines = 0
// TODO: set FontDescriptor
currentEyeLabel.font = UIFont(name: "", size: 20.0)
addSubview(currentEyeLabel)
}
func setupTopInstructionLabel() {
topInstructionLabel.textAlignment = .center
topInstructionLabel.numberOfLines = 0
topInstructionLabel.textColor = UIColor.black
// TODO: Localize
topInstructionLabel.text = "Move the dial to where you think the opening in the letter was."
// TODO: set FontDescriptor
topInstructionLabel.font = UIFont(name: "", size: 20.0)
topInstructionLabel.isHidden = true
addSubview(topInstructionLabel)
}
func setupContinueButton() {
// TODO: Localize
continueButton.diameter = 60.0
continueButton.setTitle("Next", for: UIControl.State.normal)
continueButton.backgroundColor = tintColor
continueButton.layer.cornerRadius = continueButtonCornerRadius
addSubview(continueButton)
}
private func setupConstraints() {
currentEyeLabel.translatesAutoresizingMaskIntoConstraints = false
topInstructionLabel.translatesAutoresizingMaskIntoConstraints = false
visionContentView?.translatesAutoresizingMaskIntoConstraints = false
continueButton.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
NSLayoutConstraint(item: currentEyeLabel,
attribute: .top,
relatedBy: .equal,
toItem: self,
attribute: .top,
multiplier: 1.0,
constant: eyeLabelTopPadding),
NSLayoutConstraint(item: currentEyeLabel,
attribute: .centerX,
relatedBy: .equal,
toItem: self,
attribute: .centerX,
multiplier: 1.0,
constant: 0.0),
NSLayoutConstraint(item: topInstructionLabel,
attribute: .top,
relatedBy: .equal,
toItem: currentEyeLabel,
attribute: .bottom,
multiplier: 1.0,
constant: instructionLabelTopPadding),
NSLayoutConstraint(item: topInstructionLabel,
attribute: .centerX,
relatedBy: .equal,
toItem: self,
attribute: .centerX,
multiplier: 1.0,
constant: 0.0),
NSLayoutConstraint(item: topInstructionLabel,
attribute: .width,
relatedBy: .equal,
toItem: self,
attribute: .width,
multiplier: 0.8,
constant: 0.0),
NSLayoutConstraint(item: visionContentView!,
attribute: .top,
relatedBy: .equal,
toItem: topInstructionLabel,
attribute: .bottom,
multiplier: 1.0,
constant: visionContentTopPadding),
NSLayoutConstraint(item: visionContentView!,
attribute: .centerX,
relatedBy: .equal,
toItem: self,
attribute: .centerX,
multiplier: 1.0,
constant: 0.0),
NSLayoutConstraint(item: visionContentView!,
attribute: .width,
relatedBy: .equal,
toItem: self,
attribute: .width,
multiplier: 0.8,
constant: 0.0),
NSLayoutConstraint(item: visionContentView!,
attribute: .height,
relatedBy: .equal,
toItem: self,
attribute: .height,
multiplier: 0.8,
constant: 0.0),
NSLayoutConstraint(item: continueButton,
attribute: .centerX,
relatedBy: .equal,
toItem: self,
attribute: .centerX,
multiplier: 1.0,
constant: 0.0),
NSLayoutConstraint(item: continueButton,
attribute: .bottom,
relatedBy: .equal,
toItem: self,
attribute: .bottom,
multiplier: 1.0,
constant: -20.0)
]
NSLayoutConstraint.activate(constraints)
}
}
@@ -1,270 +0,0 @@
/*
Copyright (c) 2017, 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.
*/
#import "ORKStroopContentView.h"
#import "ORKUnitLabel.h"
#import "ORKHelpers_Internal.h"
#import "ORKSkin.h"
#import "ORKBorderedButton.h"
CGFloat minimumButtonHeight = 60;
UILayoutConstraintAxis alignment = UILayoutConstraintAxisHorizontal;
CGFloat labelHeight = 250.0;
CGFloat labelWidth = 250.0;
static const CGFloat buttonStackViewSpacing = 20.0;
@implementation ORKStroopContentView {
UILabel *_colorLabel;
UIStackView *_buttonStackView;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.translatesAutoresizingMaskIntoConstraints = NO;
_colorLabel = [UILabel new];
_colorLabel.numberOfLines = 1;
_colorLabel.textAlignment = NSTextAlignmentCenter;
_colorLabel.translatesAutoresizingMaskIntoConstraints = NO;
[_colorLabel setFont:[UIFont systemFontOfSize:60]];
[_colorLabel setAdjustsFontSizeToFitWidth:YES];
ORKScreenType screenType = ORKGetVerticalScreenTypeForWindow([[[UIApplication sharedApplication] delegate] window]);
if (screenType == ORKScreenTypeiPhone5 ) {
labelWidth = 200.0;
labelHeight = 200.0;
} else {
labelWidth = 250.0;
labelHeight = 250.0;
}
[self setupDefaultButtons];
[self addSubview:_colorLabel];
[self setUpConstraints];
}
return self;
}
-(void)setupButtons {
_RButton = [[ORKBorderedButton alloc] init];
[_RButton setNormalTintColor:[UIColor blackColor]];
_RButton.translatesAutoresizingMaskIntoConstraints = NO;
[_RButton setTitle:ORKLocalizedString(@"STROOP_COLOR_RED_INITIAL", nil) forState:UIControlStateNormal];
_GButton = [[ORKBorderedButton alloc] init];
[_GButton setNormalTintColor:[UIColor blackColor]];
_GButton.translatesAutoresizingMaskIntoConstraints = NO;
[_GButton setTitle:ORKLocalizedString(@"STROOP_COLOR_GREEN_INITIAL", nil) forState:UIControlStateNormal];
_BButton = [[ORKBorderedButton alloc] init];
[_BButton setNormalTintColor:[UIColor blackColor]];
_BButton.translatesAutoresizingMaskIntoConstraints = NO;
[_BButton setTitle:ORKLocalizedString(@"STROOP_COLOR_BLUE_INITIAL", nil) forState:UIControlStateNormal];
_YButton = [[ORKBorderedButton alloc] init];
[_YButton setNormalTintColor:[UIColor blackColor]];
_YButton.translatesAutoresizingMaskIntoConstraints = NO;
[_YButton setTitle:ORKLocalizedString(@"STROOP_COLOR_YELLOW_INITIAL", nil) forState:UIControlStateNormal];
}
-(void)setupDefaultButtons {
[self setupButtons];
if (!_buttonStackView) {
_buttonStackView = [[UIStackView alloc] initWithArrangedSubviews:@[_RButton, _GButton, _BButton, _YButton]];
alignment = UILayoutConstraintAxisHorizontal;
}
minimumButtonHeight = 60;
_buttonStackView.translatesAutoresizingMaskIntoConstraints = NO;
_buttonStackView.spacing = buttonStackViewSpacing;
_buttonStackView.axis = alignment;
[self addSubview:_buttonStackView];
}
-(void)setupGridButtons {
if (_useGridLayoutForButtons) {
[self setupButtons];
[_buttonStackView removeFromSuperview];
UIStackView* stack1 = [[UIStackView alloc] initWithArrangedSubviews:@[_RButton, _GButton]];
UIStackView* stack2 = [[UIStackView alloc] initWithArrangedSubviews:@[_BButton, _YButton]];
stack1.translatesAutoresizingMaskIntoConstraints = NO;
stack1.spacing = buttonStackViewSpacing;
stack1.axis = UILayoutConstraintAxisHorizontal;
stack2.translatesAutoresizingMaskIntoConstraints = NO;
stack2.spacing = buttonStackViewSpacing;
stack2.axis = UILayoutConstraintAxisHorizontal;
_buttonStackView = [[UIStackView alloc] initWithArrangedSubviews:@[stack1,stack2]];
_buttonStackView.axis = UILayoutConstraintAxisVertical;
ORKScreenType screenType = ORKGetVerticalScreenTypeForWindow([[[UIApplication sharedApplication] delegate] window]);
if (screenType == ORKScreenTypeiPhone6) {
minimumButtonHeight = 150.0;
} else if (screenType == ORKScreenTypeiPhone5 ) {
minimumButtonHeight = 100.0;
} else {
minimumButtonHeight = 200;
}
alignment = UILayoutConstraintAxisVertical;
_buttonStackView.translatesAutoresizingMaskIntoConstraints = NO;
_buttonStackView.spacing = buttonStackViewSpacing;
_buttonStackView.axis = alignment;
[self addSubview:_buttonStackView];
[self setUpConstraints];
}
}
- (void)setUseGridLayoutForButtons:(bool)useGridLayoutForButtons{
_useGridLayoutForButtons = useGridLayoutForButtons;
[self setupGridButtons];
}
-(void)setUseTextForStimuli:(bool)useTextForStimuli{
_useTextForStimuli = useTextForStimuli;
if (!_useTextForStimuli) {
[_colorLabel setFont:[UIFont boldSystemFontOfSize:60]];
}
}
- (void)setColorLabelText:(NSString *)colorLabelText {
[_colorLabel setText:colorLabelText];
[self setNeedsDisplay];
}
- (void)setColorLabelColor:(UIColor *)colorLabelColor {
[_colorLabel setTextColor:colorLabelColor];
if (!_useTextForStimuli) {
[_colorLabel setBackgroundColor:colorLabelColor];
}
[self setNeedsDisplay];
}
- (NSString *)colorLabelText {
return _colorLabel.text;
}
- (UIColor *)colorLabelColor {
return _colorLabel.textColor;
}
- (void)setUpConstraints {
NSMutableArray *constraints = [[NSMutableArray alloc] init];
NSDictionary *views = NSDictionaryOfVariableBindings(_colorLabel, _buttonStackView);
int bottomStackViewSpace = _useGridLayoutForButtons ? 90 : 30;
NSString * constraintString = [NSString stringWithFormat: @"V:|-(==30)-[_colorLabel]-(>=10)-[_buttonStackView]-(==%d)-|", bottomStackViewSpace];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:constraintString
options:NSLayoutFormatAlignAllCenterX
metrics:nil
views:views]];
NSArray *baseLayouts = @[[NSLayoutConstraint constraintWithItem:_buttonStackView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:minimumButtonHeight],
[NSLayoutConstraint constraintWithItem:_buttonStackView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[constraints addObjectsFromArray:baseLayouts];
if (!_useTextForStimuli) {
[constraints addObjectsFromArray: @[[NSLayoutConstraint constraintWithItem:_colorLabel
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:labelWidth],
[NSLayoutConstraint constraintWithItem:_colorLabel
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant: labelHeight],
[NSLayoutConstraint constraintWithItem:_buttonStackView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:minimumButtonHeight]]];
}
for (ORKBorderedButton *button in @[_RButton, _GButton, _BButton, _YButton]) {
[constraints addObject:[NSLayoutConstraint constraintWithItem:button
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:button
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0.0]];
}
[self addConstraints:constraints];
[NSLayoutConstraint activateConstraints:constraints];
}
@end
@@ -1,168 +0,0 @@
/*
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 ORKSwiftStroopContentView: ORKActiveStepCustomView {
public var colorLabelText: String?
public var colorLabelColor: UIColor?
public let redButton: ORKBorderedButton = {
let button = ORKBorderedButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle(ORKSwiftLocalizedString("STROOP_COLOR_RED_INITIAL", ""), for: .normal)
return button
}()
public let greenButton: ORKBorderedButton = {
let button = ORKBorderedButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle(ORKSwiftLocalizedString("STROOP_COLOR_GREEN_INITIAL", ""), for: .normal)
return button
}()
public let blueButton: ORKBorderedButton = {
let button = ORKBorderedButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle(ORKSwiftLocalizedString("STROOP_COLOR_BLUE_INITIAL", ""), for: .normal)
return button
}()
public let yellowButton: ORKBorderedButton = {
let button = ORKBorderedButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle(ORKSwiftLocalizedString("STROOP_COLOR_YELLOW_INITIAL", ""), for: .normal)
return button
}()
private let colorLabel: UILabel = {
let label = UILabel()
let colorLabelFontSize: CGFloat = 60.0
label.numberOfLines = 1
label.text = " "
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: colorLabelFontSize)
label.adjustsFontSizeToFitWidth = true
return label
}()
private let buttonStackView: UIStackView
private let minimumButtonHeight: CGFloat = 60.0
private let buttonStackViewSpacing: CGFloat = 20.0
private override init(frame: CGRect) {
buttonStackView = UIStackView(arrangedSubviews: [redButton, greenButton, blueButton, yellowButton])
super.init(frame: frame)
setup()
}
internal required init?(coder aDecoder: NSCoder) {
buttonStackView = UIStackView(arrangedSubviews: [redButton, greenButton, blueButton, yellowButton])
super.init(coder: aDecoder)
setup()
}
internal func setup() {
self.translatesAutoresizingMaskIntoConstraints = false
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
buttonStackView.spacing = buttonStackViewSpacing
buttonStackView.axis = .horizontal
self.addSubview(colorLabel)
self.addSubview(buttonStackView)
setUpConstraints()
}
internal func setColorLabelText(colorLabelText text: String) {
colorLabelText = text
colorLabel.text = text
setNeedsDisplay()
}
internal func setColorLabelColor(colorLabelColor color: UIColor) {
colorLabelColor = color
colorLabel.textColor = color
setNeedsDisplay()
}
internal func getColorLabelText() -> String {
return colorLabel.text!
}
internal func getColorLabelColor() -> UIColor {
return colorLabel.textColor
}
internal func setUpConstraints() {
var constraints = [NSLayoutConstraint]()
var views = [String: Any]()
views["colorLabel"] = colorLabel
views["buttonStackView"] = buttonStackView
constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|-(==30)-[colorLabel]-(>=10)-[buttonStackView]-(==30)-|",
options: .alignAllCenterX,
metrics: nil,
views: views)
constraints += [
NSLayoutConstraint(item: buttonStackView as Any,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1.0,
constant: minimumButtonHeight),
NSLayoutConstraint(item: buttonStackView as Any,
attribute: .centerX,
relatedBy: .equal,
toItem: self,
attribute: .centerX,
multiplier: 1.0,
constant: 0.0)
]
for button in [redButton, greenButton, blueButton, yellowButton] {
constraints += [
NSLayoutConstraint(item: button,
attribute: .width,
relatedBy: .equal,
toItem: button,
attribute: .height,
multiplier: 1.0,
constant: 0.0)
]
}
self.addConstraints(constraints)
NSLayoutConstraint.activate(constraints)
}
}
@@ -1,106 +0,0 @@
/*
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.
*/
import ResearchKit.Private
public class ORKSwiftStroopResult: ORKResult {
public var startTime: TimeInterval?
public var endTime: TimeInterval?
public var color: String?
public var text: String?
public var colorSelected: String?
enum Keys: String {
case startTime
case endTime
case color
case text
case colorSelected
}
public override init(identifier: String) {
super.init(identifier: identifier)
}
public override func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
aCoder.encode(startTime, forKey: Keys.startTime.rawValue)
aCoder.encode(endTime, forKey: Keys.endTime.rawValue)
aCoder.encode(color, forKey: Keys.color.rawValue)
aCoder.encode(text, forKey: Keys.text.rawValue)
aCoder.encode(colorSelected, forKey: Keys.colorSelected.rawValue)
}
public required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
startTime = aDecoder.decodeObject(forKey: Keys.startTime.rawValue) as? Double
endTime = aDecoder.decodeObject(forKey: Keys.endTime.rawValue) as? Double
color = aDecoder.decodeObject(forKey: Keys.color.rawValue) as? String
text = aDecoder.decodeObject(forKey: Keys.text.rawValue) as? String
colorSelected = aDecoder.decodeObject(forKey: Keys.colorSelected.rawValue) as? String
}
public class func supportsSecureCoding() -> Bool {
return true
}
override public func isEqual(_ object: Any?) -> Bool {
let isParentSame = super.isEqual(object)
if let castObject = object as? ORKSwiftStroopResult {
return (isParentSame &&
(startTime == castObject.startTime) &&
(endTime == castObject.endTime) &&
(color == castObject.color) &&
(text == castObject.text) &&
(colorSelected == castObject.colorSelected))
}
return true
}
override public func copy(with zone: NSZone? = nil) -> Any {
if let result = super.copy(with: zone) as? ORKSwiftStroopResult {
result.startTime = startTime
result.endTime = endTime
result.color = color
result.text = text
result.colorSelected = colorSelected
return result
} else {
return super.copy(with: zone)
}
}
public override func description(withNumberOfPaddingSpaces numberOfPaddingSpaces: UInt) -> String {
return "\(descriptionPrefix(withNumberOfPaddingSpaces: numberOfPaddingSpaces)); color: \(color ?? "") text: \(text ?? "") colorSelected: \(colorSelected ?? "") \(descriptionSuffix())"
}
}
@@ -1,94 +0,0 @@
/*
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.
*/
import Foundation
public class ORKSwiftStroopStep: ORKActiveStep {
public var numberOfAttempts = 0
private let minimumAttempts = 10
enum Key: String {
case numberOfAttempts
}
public override class func stepViewControllerClass() -> AnyClass {
return ORKSwiftStroopStepViewController.self
}
public class func supportsSecureCoding() -> Bool {
return true
}
public override init(identifier: String) {
super.init(identifier: identifier)
shouldVibrateOnStart = true
shouldShowDefaultTimer = false
shouldContinueOnFinish = true
stepDuration = TimeInterval(NSIntegerMax)
}
public override func validateParameters() {
super.validateParameters()
assert(numberOfAttempts >= minimumAttempts, "number of attempts should be greater or equal to \(minimumAttempts)")
}
public override func startsFinished() -> Bool {
return false
}
public override var allowsBackNavigation: Bool {
return false
}
public override func copy(with zone: NSZone? = nil) -> Any {
let stroopStep = super.copy(with: zone)
return stroopStep
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
numberOfAttempts = aDecoder.decodeInteger(forKey: Key.numberOfAttempts.rawValue)
}
public override func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
aCoder.encode(numberOfAttempts, forKey: Key.numberOfAttempts.rawValue)
}
public override func isEqual(_ object: Any?) -> Bool {
if let object = object as? ORKSwiftStroopStep {
return numberOfAttempts == object.numberOfAttempts
}
return false
}
}
@@ -1,213 +0,0 @@
/*
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.
*/
import ResearchKit.Private
public class ORKSwiftStroopStepViewController: ORKActiveStepViewController {
private let stroopContentView = ORKSwiftStroopContentView()
private var colors = [String: UIColor]()
private var differentColorLabels = [String: [UIColor]]()
private var questionNumber = 0
private let red = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
private let green = UIColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0)
private let blue = UIColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0)
private let yellow = UIColor(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0)
private let redString = ORKSwiftLocalizedString("STROOP_COLOR_RED", "")
private let greenString = ORKSwiftLocalizedString("STROOP_COLOR_GREEN", "")
private let blueString = ORKSwiftLocalizedString("STROOP_COLOR_BLUE", "")
private let yellowString = ORKSwiftLocalizedString("STROOP_COLOR_YELLOW", "")
private var nextQuestionTimer: Timer?
private var results: NSMutableArray?
private var startTime: TimeInterval?
private var endTime: TimeInterval?
public override init(step: ORKStep?) {
super.init(step: step)
suspendIfInactive = true
}
internal required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func stroopStep() -> ORKSwiftStroopStep {
return step as! ORKSwiftStroopStep
}
public override func viewDidLoad() {
super.viewDidLoad()
results = NSMutableArray()
colors[redString] = red
colors[blueString] = blue
colors[yellowString] = yellow
colors[greenString] = green
differentColorLabels[redString] = [blue, green, yellow]
differentColorLabels[blueString] = [red, green, yellow]
differentColorLabels[yellowString] = [red, blue, green]
differentColorLabels[greenString] = [red, blue, yellow]
activeStepView?.activeCustomView = stroopContentView
activeStepView?.customContentFillsAvailableSpace = true
stroopContentView.redButton.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
stroopContentView.greenButton.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
stroopContentView.blueButton.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
stroopContentView.yellowButton.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
}
@objc
private func buttonPressed(sender: Any) {
if stroopContentView.colorLabelText != " " {
setButtonDisabled()
if let button = sender as? ORKBorderedButton {
if button == stroopContentView.redButton {
createResult(color: (colors as NSDictionary).allKeys(for: stroopContentView.colorLabelColor!).first
as? String ?? "", withText: stroopContentView.colorLabelText!,
withColorSelected: redString)
} else if button == stroopContentView.greenButton {
createResult(color: (colors as NSDictionary).allKeys(for: stroopContentView.colorLabelColor!).first
as? String ?? "", withText: stroopContentView.colorLabelText!,
withColorSelected: greenString)
} else if button == stroopContentView.blueButton {
createResult(color: (colors as NSDictionary).allKeys(for: stroopContentView.colorLabelColor!).first
as? String ?? "", withText: stroopContentView.colorLabelText!,
withColorSelected: blueString)
} else if button == stroopContentView.yellowButton {
createResult(color: (colors as NSDictionary).allKeys(for: stroopContentView.colorLabelColor!).first
as? String ?? "", withText: stroopContentView.colorLabelText!,
withColorSelected: yellowString)
}
nextQuestionTimer = Timer.scheduledTimer(timeInterval: 0.5,
target: self,
selector: #selector(startNextQuestionOrFinish),
userInfo: nil,
repeats: false)
}
}
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
start()
}
public override func stepDidFinish() {
super.stepDidFinish()
stroopContentView.finishStep(self)
goForward()
}
public override var result: ORKStepResult? {
let stepResult = super.result
if results != nil {
stepResult?.results = results?.copy() as? [ORKResult]
}
return stepResult!
}
public override func start() {
super.start()
startQuestion()
}
private func createResult(color: String, withText text: String, withColorSelected colorSelected: String) {
let stroopResult = ORKSwiftStroopResult(identifier: (step!.identifier))
stroopResult.startTime = startTime
stroopResult.endTime = ProcessInfo.processInfo.systemUptime
stroopResult.color = color
stroopResult.text = text
stroopResult.colorSelected = colorSelected
results?.add(stroopResult)
}
@objc
private func startNextQuestionOrFinish() {
if nextQuestionTimer != nil {
nextQuestionTimer?.invalidate()
nextQuestionTimer = nil
}
questionNumber += 1
if questionNumber == stroopStep().numberOfAttempts {
finish()
} else {
startQuestion()
}
}
private func startQuestion() {
let pattern: Int = Int(arc4random()) % 2
if pattern == 0 {
let index: Int = Int(arc4random()) % differentColorLabels.keys.count
let text = Array(differentColorLabels.keys)[index]
stroopContentView.setColorLabelText(colorLabelText: text)
let color = colors[text]!
stroopContentView.colorLabelColor = color
stroopContentView.setColorLabelColor(colorLabelColor: color)
} else {
let index: Int = Int(arc4random()) % differentColorLabels.keys.count
let text = Array(differentColorLabels.keys)[index]
stroopContentView.setColorLabelText(colorLabelText: text)
let colorArray = differentColorLabels[text]!
let randomColorIndex = Int(arc4random()) % colorArray.count
let color = colorArray[randomColorIndex]
stroopContentView.setColorLabelColor(colorLabelColor: color)
}
setButtonsEnabled()
startTime = ProcessInfo.processInfo.systemUptime
}
private func setButtonDisabled() {
stroopContentView.redButton.isEnabled = false
stroopContentView.greenButton.isEnabled = false
stroopContentView.blueButton.isEnabled = false
stroopContentView.yellowButton.isEnabled = false
}
private func setButtonsEnabled() {
stroopContentView.redButton.isEnabled = true
stroopContentView.greenButton.isEnabled = true
stroopContentView.blueButton.isEnabled = true
stroopContentView.yellowButton.isEnabled = true
}
}
-105
View File
@@ -1,105 +0,0 @@
/*
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 TrackLayer: CAShapeLayer {
struct Setting {
var startAngle = Double()
var barWidth = CGFloat()
var barColor = UIColor()
var trackingColor = UIColor()
}
internal var setting = Setting()
internal var degree: Double = 0
internal var hollowRadius: CGFloat {
return (bounds.width * 0.5) - setting.barWidth
}
internal var currentCenter: CGPoint {
return CGPoint(x: bounds.midX, y: bounds.midY)
}
internal var hollowRect: CGRect {
return CGRect(
x: currentCenter.x - hollowRadius,
y: currentCenter.y - hollowRadius,
width: hollowRadius * 2.0,
height: hollowRadius * 2.0)
}
internal init(bounds: CGRect, setting: Setting) {
super.init()
self.bounds = bounds
self.setting = setting
cornerRadius = bounds.size.width * 0.5
masksToBounds = true
position = currentCenter
backgroundColor = setting.barColor.cgColor
mask()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override internal func draw(in ctx: CGContext) {
drawTrack(ctx: ctx)
}
private func mask() {
let maskLayer = CAShapeLayer()
maskLayer.bounds = bounds
let ovalRect = hollowRect
let path = UIBezierPath(ovalIn: ovalRect)
path.append(UIBezierPath(rect: maskLayer.bounds))
maskLayer.path = path.cgPath
maskLayer.position = currentCenter
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
mask = maskLayer
}
private func drawTrack(ctx: CGContext) {
let adjustDegree = Math.adjustDegree(setting.startAngle, degree: degree)
let centerX = currentCenter.x
let centerY = currentCenter.y
let radius = min(centerX, centerY)
ctx.setFillColor(setting.trackingColor.cgColor)
ctx.beginPath()
ctx.move(to: CGPoint(x: centerX, y: centerY))
ctx.addArc(center: CGPoint(x: centerX, y: centerY),
radius: radius,
startAngle: CGFloat(Math.degreesToRadians(setting.startAngle)),
endAngle: CGFloat(Math.degreesToRadians(adjustDegree)),
clockwise: false)
ctx.closePath()
ctx.fillPath()
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ContrastExam.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ContrastExam@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ContrastExam@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "VisualExam.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "VisualExam@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "VisualExam@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "touchAbilityHorizontalScroll.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "touchAbilityHorizontalScroll@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "touchAbilityHorizontalScroll@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "touchAbilityInstruction.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "touchAbilityInstruction@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "touchAbilityInstruction@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "touchAbilityPinch.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "touchAbilityPinch@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "touchAbilityPinch@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "touchAbilityRotation.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "touchAbilityRotation@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "touchAbilityRotation@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "touchAbilitySwipe1.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "touchAbilitySwipe1@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "touchAbilitySwipe1@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "touchAbilitySwipe2.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "touchAbilitySwipe2@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "touchAbilitySwipe2@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "touchAbilitySwipe3.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "touchAbilitySwipe3@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "touchAbilitySwipe3@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "touchAbilitySwipe4.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "touchAbilitySwipe4@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "touchAbilitySwipe4@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "touchAbilityTap.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "touchAbilityTap@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "touchAbilityTap@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "touchAbilityVerticalScroll.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "touchAbilityVerticalScroll@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "touchAbilityVerticalScroll@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

@@ -1,21 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "iCNDialPointerWithShadow.pdf",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -1,365 +0,0 @@
%PDF-1.5 %âãÏÓ
30 0 obj <</Metadata 33 0 R/OCProperties<</D<</ON[34 0 R 58 0 R]/Order 59 0 R/RBGroups[]>>/OCGs[34 0 R 58 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 33 0 obj <</Length 7573/Subtype/XML/Type/Metadata>>stream
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c132 79.159284, 2016/04/19-13:13:40 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
xmlns:xmpGImg="http://ns.adobe.com/xap/1.0/g/img/"
xmlns:pdf="http://ns.adobe.com/pdf/1.3/"
xmlns:xmpTPg="http://ns.adobe.com/xap/1.0/t/pg/"
xmlns:stDim="http://ns.adobe.com/xap/1.0/sType/Dimensions#"
xmlns:xmpG="http://ns.adobe.com/xap/1.0/g/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">
<xmp:CreateDate>2016-11-10T19:24:21Z</xmp:CreateDate>
<xmp:ModifyDate>2016-11-10T11:31:44-08:00</xmp:ModifyDate>
<xmp:MetadataDate>2016-11-10T11:31:44-08:00</xmp:MetadataDate>
<xmp:Thumbnails>
<rdf:Alt>
<rdf:li rdf:parseType="Resource">
<xmpGImg:width>144</xmpGImg:width>
<xmpGImg:height>256</xmpGImg:height>
<xmpGImg:format>JPEG</xmpGImg:format>
<xmpGImg:image>/9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA&#xA;AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK&#xA;DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f&#xA;Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAACQAwER&#xA;AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA&#xA;AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB&#xA;UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE&#xA;1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ&#xA;qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy&#xA;obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp&#xA;0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo&#xA;+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7&#xA;FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F&#xA;XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX&#xA;Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY&#xA;q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq&#xA;7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7&#xA;FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F&#xA;XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX&#xA;Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXV&#xA;GKuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVRvLh7e2kmSJp3RSVhSgZj4AsQMjMkCwLZRAJ&#xA;omnk3mT8wfNdy7W0cZ0mL+RKiYjbrIwB7fsgZzOr7Szk8NeH9/z/AFU9LpOzcNcV+J93y/WxGC4v&#xA;4LtbyKZ0ulbmJgx5cvGuayOQg8QO7tJYwY8JGz6F024ludOtbiZQk0sSPIgBADMoJFDv1zt8UjKA&#xA;J5kPD5YiMiByBRGWMHYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FUv1LQdL1KPhdQK/gSBUZDJij&#xA;MVIWGePJKBuJopBa/lnoMF6LluUsanksDGqVHj4/TmDHsrAJcVfDo50u1M5jw38erLwAAAOgzYuu&#xA;dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVd&#xA;irsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdi&#xA;rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir&#xA;sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirs&#xA;VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsV&#xA;dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVd&#xA;irsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdi&#xA;rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir&#xA;sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirs&#xA;VdirsVdirsVdirsVdirsVdirsVdirsVdirsVf//Z</xmpGImg:image>
</rdf:li>
</rdf:Alt>
</xmp:Thumbnails>
<pdf:Producer>Mac OS X 10.12.1 Quartz PDFContext</pdf:Producer>
<xmpTPg:NPages>1</xmpTPg:NPages>
<xmpTPg:HasVisibleTransparency>True</xmpTPg:HasVisibleTransparency>
<xmpTPg:HasVisibleOverprint>False</xmpTPg:HasVisibleOverprint>
<xmpTPg:MaxPageSize rdf:parseType="Resource">
<stDim:w>49.000000</stDim:w>
<stDim:h>35.000000</stDim:h>
<stDim:unit>Points</stDim:unit>
</xmpTPg:MaxPageSize>
<xmpTPg:PlateNames>
<rdf:Seq>
<rdf:li>Cyan</rdf:li>
<rdf:li>Magenta</rdf:li>
<rdf:li>Yellow</rdf:li>
<rdf:li>Black</rdf:li>
</rdf:Seq>
</xmpTPg:PlateNames>
<xmpTPg:SwatchGroups>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<xmpG:groupName>Default Swatch Group</xmpG:groupName>
<xmpG:groupType>0</xmpG:groupType>
</rdf:li>
</rdf:Seq>
</xmpTPg:SwatchGroups>
<dc:format>application/pdf</dc:format>
<xmpMM:RenditionClass>proof:pdf</xmpMM:RenditionClass>
<xmpMM:DocumentID>uuid:2efd0779-15bc-7d48-9832-af59b6fbb134</xmpMM:DocumentID>
<xmpMM:InstanceID>uuid:95f52edf-f897-4942-acec-80c9294e7f9d</xmpMM:InstanceID>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?> endstream endobj 3 0 obj <</Count 1/Kids[2 0 R]/MediaBox[0 0 49 32]/Type/Pages>> endobj 2 0 obj <</ArtBox[0.0 0.0 49.0 35.0]/BleedBox[0.0 0.0 49.0 35.0]/Contents 60 0 R/Group 61 0 R/LastModified(D:20161110113144-08'00')/MediaBox[0.0 0.0 49.0 35.0]/Parent 3 0 R/PieceInfo<</Illustrator 62 0 R>>/Resources<</ColorSpace<</CS0 63 0 R>>/ExtGState<</GS0 64 0 R/GS1 65 0 R>>/ProcSet[/PDF/ImageC/ImageI]/Properties<</MC0 58 0 R>>/XObject<</Fm0 66 0 R/Im0 67 0 R>>>>/Thumb 68 0 R/TrimBox[0.0 0.0 49.0 35.0]/Type/Page>> endobj 60 0 obj <</Filter/FlateDecode/Length 444>>stream
H‰ì”ÏŽ1 Æïy
¿ÀxâØ‰“+]X!±BË"ñÕ²¶ …¯Ï—ÌŸ¶Ü8 q@­ÒübÇö|öt~ ùîéÕÍÂKˆÉi¦ïá}ÅÑ|ûééLßÍoN‘n¾…{|ÎÎvå,Ý9u«:MÚ¿ÇS˜ß^ÞÕÈB©r-™NgòFÏÁ8åJâlM€©±‰’sÒ„¯z¡ÂŽÄ*¬ {ÓJšðã+ÃâZeO›«)W€gäßȹ6\¼Âç ª’LkÔ¤–¶”+í9WFRárfT^sî$¥Ñ¥Ç!Ì­Rä V14áiWtŠì¦$Ký³ô-õ?Q…”¬ô1Ê֔ȭâ•ÜWA9nq˜é'íÁ_HF¿dT£/…!× XÙ{SY²i/Ñެ¹ì¡rIu%QHUi2¤ÞAPŽ÷ÞÐ×Öh¹8%˜¶ Á>;FNˆÐ SYJí»Ò˜³0¥ÌBMh®º¡ ¨Ÿ#Z7vX¿„‡ÿCø÷†ðDµ¡iÇT·‡ü Kç\åB×J-˜û¼×ñré•Z˜¼ËÔ4ݬ‡Ðì‹Z¯ïðŸ{~ 0ÃcG endstream endobj 61 0 obj <</CS/DeviceRGB/I false/K false/S/Transparency>> endobj 68 0 obj <</BitsPerComponent 8/ColorSpace 69 0 R/Filter[/ASCII85Decode/FlateDecode]/Height 4/Length 45/Width 6>>stream
8;Xp,rVG=0fB4-G1O%.7j)^7,4[/$iLj'j^^]<.&&T.~> endstream endobj 69 0 obj [/Indexed/DeviceRGB 255 70 0 R] endobj 70 0 obj <</Filter[/ASCII85Decode/FlateDecode]/Length 428>>stream
8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0
b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup`
E1r!/,*0[*9.aFIR2&b-C#s<Xl5FH@[<=!#6V)uDBXnIr.F>oRZ7Dl%MLY\.?d>Mn
6%Q2oYfNRF$$+ON<+]RUJmC0I<jlL.oXisZ;SYU[/7#<&37rclQKqeJe#,UF7Rgb1
VNWFKf>nDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j<etJICj7e7nPMb=O6S7UOH<
PO7r\I.Hu&e0d&E<.')fERr/l+*W,)q^D*ai5<uuLX.7g/>$XKrcYp0n+Xl_nU*O(
l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 66 0 obj <</BBox[-0.742523 34.3421 48.8291 1.4379]/Group 71 0 R/Length 664/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]/Resources<</ExtGState<</GS0 65 0 R>>>>/Subtype/Form>>stream
q
30.1 28.865 m
30.1 28.879 l
4.258 17.491 l
29.413 7.232 l
30.376 6.734 31.39 6.438 32.437 6.438 c
32.437 6.438 l
38.729 6.438 43.829 11.565 43.829 17.89 c
43.829 17.89 l
43.829 24.215 38.729 29.342 32.437 29.342 c
32.437 29.342 l
31.637 29.342 30.855 29.169 30.1 28.865 c
W n
0.898 0.506 0.145 rg
/GS0 gs
-0.743 1.438 49.572 32.904 re
f
Q
q
2 3 45 29.001 re
W n
0.961 0.651 0.137 RG
0.5 w 10 M 0 j 0 J []0 d
/GS0 gs
q 1 0 0 1 30.1007 28.8646 cm
0 0 m
0.754 0.304 1.536 0.477 2.337 0.477 c
8.628 0.477 13.728 -4.65 13.728 -10.975 c
13.728 -17.299 8.628 -22.427 2.337 -22.427 c
1.29 -22.427 0.275 -22.13 -0.688 -21.633 c
-25.843 -11.374 l
0 0.015 l
0 0 l
h
S
Q
Q
endstream endobj 67 0 obj <</BitsPerComponent 8/ColorSpace 63 0 R/Decode[0.0 255.0]/Filter/FlateDecode/Height 37/Intent/RelativeColorimetric/Length 105/Name/X/SMask 72 0 R/Subtype/Image/Type/XObject/Width 54>>stream
H‰ÜÓ9À DQ¸ÿ¥Ó¡$x;Büþ5Ö¸µmê£ñ]Ÿ«ÉbC$„Q˜2‘Òfby)×ÜY •2ßT®V^¾¸êÿ2a„¤H@¢°£@i"g¡'´É] ,O endstream endobj 63 0 obj [/Indexed/DeviceRGB 1 73 0 R] endobj 72 0 obj <</BitsPerComponent 8/ColorSpace/DeviceGray/DecodeParms<</BitsPerComponent 4/Colors 1/Columns 54>>/Filter/FlateDecode/Height 37/Intent/RelativeColorimetric/Length 639/Name/X/Subtype/Image/Type/XObject/Width 54>>stream
H‰Ì•Ùzª@„ã‚€ˆ¸! FT” ¢‰ûrŒ¨HÞÿq2ƒHЃKîÒ÷ÿW]=Õ=//¦"~ý‰Æâ°b±è“œ‹$0œ ˆ$Ž"Oq.ƒ&ÉLžfŠtŽ"0È=fŒ òìk½Ùj |™Îí€ÖRºT»£ñxØo7Ê»ìàd¶XiHýñâs³Ù(«Ù‡ÄÓ·0·54™Î±ÕfçcºÚèûƒi;m5éÖ™4 ¡`kÐNãEy8ÿTwëè8ÎÑ6wÊL®Ñ©ÄµØÉì”ëo½ñb­ïMûè|¹åØûÍ´óšÃã‘+0iœÌ1•Fû}òo£¦í!¶X2(v¶“‡v³•º…­}˱ô…\Íb¾³óëÀ¤¡mw°¯PGc=hÂoøA
Ø“^*A;b¦:}cI$z–Š%ˆ,W“ ­aÙ!,KŸwÊT"êKáSkæŠn\Û ”½]v+êSH2[äÉJÛ›·„N”¤\-iæpÓG-‚º¾X^ìÎqƒS›Jy¦Ü&aŒZÝÁôô¾!àÑPFÍ"ˆ,sŠùZ t¬í²Ç_DÊ[o¥@8À \
:¶¡ŒEî*õ/ïé<ó*´ß¡à>  u.óùävpv“å ÂU± uÑo0d"dÁ¼Ý‡÷¢
'KEÛ¦e™†¾žõŽÂâá‡ÊK?8åºØ‚Šªkêz1ê6Ø þèp@A®Úl÷‡“Ùl2Åj‘‘»·íG°Ä ¢$‰B•ͥ୹ÍpCgI<ñúĉì9Æã€"*¯ü¯¾€þê'úõ-Àz¬ endstream endobj 73 0 obj <</Length 6>>stream
ÿÿÿ endstream endobj 71 0 obj <</I true/K false/S/Transparency/Type/Group>> endobj 65 0 obj <</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask/None/Type/ExtGState/ca 1.0/op false>> endobj 58 0 obj <</Intent 74 0 R/Name(Layer 1)/Type/OCG/Usage 75 0 R>> endobj 74 0 obj [/View/Design] endobj 75 0 obj <</CreatorInfo<</Creator(Adobe Illustrator 20.1)/Subtype/Artwork>>>> endobj 64 0 obj <</AIS false/BM/Normal/CA 0.0/OP false/OPM 1/SA true/SMask/None/Type/ExtGState/ca 0.0/op false>> endobj 62 0 obj <</LastModified(D:20161110113144-08'00')/Private 76 0 R>> endobj 76 0 obj <</AIMetaData 77 0 R/AIPDFPrivateData1 78 0 R/AIPDFPrivateData2 79 0 R/AIPDFPrivateData3 80 0 R/AIPDFPrivateData4 81 0 R/ContainerVersion 11/CreatorVersion 20/NumBlock 4/RoundtripVersion 20>> endobj 77 0 obj <</Length 1246>>stream
%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 17.0 %%AI8_CreatorVersion: 20.1.0 %%For: (Stephaine) () %%Title: (iCNDialPointerWithShadow.pdf) %%CreationDate: 11/10/16 11:31 AM %%Canvassize: 16383 %%BoundingBox: -259 -290 61 278 %%HiResBoundingBox: -259 -290 61 278 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 13.0 %AI12_BuildNumber: 174 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: 0 0 49 35 %AI3_TemplateBox: 23.5 16.5 23.5 16.5 %AI3_TileBox: -353.5 -270.5 380.5 305.5 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 2 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI17_Begin_Content_if_version_gt:17 1 %AI9_OpenToView: -53.916666666667 73.916666666667 12 2280 1248 26 1 0 -4 38 0 0 0 1 1 0 1 1 0 1 %AI17_Alternate_Content %AI9_OpenToView: -53.916666666667 73.916666666667 12 2280 1248 26 1 0 -4 38 0 0 0 1 1 0 1 1 0 1 %AI17_End_Versioned_Content %AI5_OpenViewLayers: 7 %%PageOrigin:-309 -209 %AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 78 0 obj <</Length 2080>>stream
%%BoundingBox: -259 -290 61 278 %%HiResBoundingBox: -259 -290 61 278 %AI7_Thumbnail: 72 128 8 %%BeginData: 1948 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFF %FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDF6FFCFFD44FFC9C299C1C9 %FD40FFA8C99999989F9899A8FD3EFFC9C198C199C199C198C9FD3EFFCFA0 %A098C1999F98BBA7FD40FFA9CAA0C199C1A0FD42FFAFFFA8A7A1FDFCFFFD %FCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFD %FCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDDCFFFF %%EndData endstream endobj 79 0 obj <</Filter[/FlateDecode]/Length 16099>>stream
H‰ÜW{oâ:ß/Àwðj5«V „$½A;îö ÓÛÕh„Lâ‚o9N[æÓï±ó ¥åqïÌlg¤@|~çø¼}üæïÃq³Ë)mvZj¼ys¬(ÑR!»ŠN9ÏR­ÌÒÁèa¿å¨L
à5U)“â¹N [â{Ã}0Ö4™&è!:8„Õ+¦9…uv|qÂJ&4U¿3=ÏI,ZI|{XîOˆ8Æmì´qþu0êŸ÷$MÙWCïu¬ d&b&fùx„š®Â#tP#×€þ‘ t"£lA…*Ñ4=\ªô/‰@çd‚þC9—hÀIt×?x“÷ŒS0yA4Âã€þ)v'ƒŒñø"[L)8û]³Ü™X‘ŸR³ìON°2¦Zƒr°¡qâèà®,ÚïÁç1ðÑ—ÃB¬’É‚¨»4‡uCÔñrÊ]$<iMv;-<ê_r§t<Ciº¾?À>¯Ä­}8BRÐÜ }¥ÇyDº]ÇÉŸ9e”qª> ¦A;×,…¹ÎeL9à+þ÷œÌÒÒP¼zæ€+¢fTC%Ï´M¸ ÜÜ|F–ÔÄÊJÃþd^°ä˜Ðv;¹ÏÓt2ÓGØÏaáä2¡âJ^[Sš`yˆ{åÇGþÚ;v‘ë “Û Û³º5»à¤Jc¼zjô9ä¸ÿ—ªüö}'âIQ˜4®ïíÙ½ÍÎ¥Ç|H¶!¤ß¥bಣfÇ1eá„yf~P,^%¦ï¢ ØÀ´ æƒq n¸ïJh?~{îºû¬änƒÑ`Hewr|^+§u>[Àôc¹0Yšš¾`ò
‰ËYN«þ[
°gIî—<c ¡‡
:Èl\XJ0ò H”Ì’Sq+yg¼¦´?Èù]Nÿ€ht¶Ñ•"€÷
Ó",9Ü*ŒSåDà´¯åïnîz -hÅž¯¾÷”ˤ&6Çí–7äD…ìz%îŒÝzº^ 4@ªoöÙ–€;¬ Y“¾P#íÞè Μuv(ý Õ]µaíhk‘d‡ÔñÕÑ|]n±úM’‡DÏá<¡"N+ùëÊËÆóùÚnû çl¦H2g¨,£+)y%ûzµOfI†s÷–ãȦzi·§¤j£bù'ÛchÄ¥Ècò|§°¾^9ÏOºWÅóÒ>@üUö/SÉYºX…¾¶2„2d§ãeªé>=®pð»˜A¡nhJ[1ãåƦ[ËÒyËD :Θ¦«J”‹ÄŒ¤&à„Z Jä¸èÙüv.5›Û¬0§˜L/9Mí ù ì :j|†3€d\9Dí ² èm£=f0&Òâ Ë†S.0º!°0j8-¿ú.îöà·ö<8©{~ˆÃ®ëc¯×í]»â­×u½ ôzæÄÜõ|:îô0.Dßôåœ/KxùüùvÐ9úüÅA1,ߌV‰¸ÑÎOŸß¨ VÀ¯µ´²v§Ë†„ÃÐ@­…Ãéw¶i}>·¼ùÚx6·£ÆpP)[êôg{âêÑ|æ ø+œ”ó·b­-n^èZ^à2Go&íò’̼±Èxƒ¨eþÞWŠ,_$aÔQ¡Búp } +pÇqòE{“Q~%Gù TÊ” –êæ<\‹öæÁ9O€{
°ß©1Z°½ÖäÌ¥ôö)XvpÂR¸r-ω Á´º©$*F ³ 7¡n>c þš¡S9 l@„!2-ŠØ®0¿ÏJ¤uÙÓýÍe°¯(éÛy©Páó9Y¶@«ûÓ—çº@ˆ1ªBdgcTÌÜлàÒ—Rj
{N8:§ÐÞGz/ ¼6Ík»åç Çe¦“Lïà)ÍzI¹3"f™™¸‡21 ÙàÝ
"(7&0@h‰fp•A)Ìâ•ÜÜÝVp\
f°;Ü×ÐÔL[vˆ7àžç™Ô©“2¢‘¹ÉB‰+çHÏíºÁf¤ ‚3º'´ºA×;!£; œåçQEî¹éLQ*ö´zeËNhMlÍš%pò)Ë"ãdtüÄ`y
®WôQÛ£˜LgºH|Üëjï2Eމ¸'鸊wûæüì’ýÅþõ¸àÈMHwŦ™¦iÁWëJ?XÄw_CEsÆcU:½,‹’jz™”ýáŸ"Ü•þ]¡luè=©Bm×Ó 8QkU%ðí/ï)³½ïáp"ô¦¼IîöPý’ô¯Ú(¤Ø¤rÝ>-Ç´¥Ý¶•Èïþo´oµ~¯¸2‡Â>‰¿3’¿R¡Ýï]êúÊYlÌ‹²TËÅëv²—‡G)1w53QCëØ7x]€.?*ÿUšÞ>üħñ+—AÊYô«÷bn–Åmh“™sÊfó}
«B¾nÿí¸Î.“X¬çûXT_× ê"²Éœ©ÔpØœÑ[}©ØŒ‰},{ÎóºF6]/ܶǽZÌë[îLÀå>–,ŠÃÞv¹±ÌTD2ñ«Ÿöp&½¶
ªI ÃÏ·êÿˆe”-¨Ø«;×À–ÙuZ¸åÔ8ïú§ï3ÎO
Ø5U)Ô P˾ê÷üBá!Ð
5uO?(OF¦ñP´4ÌÙ†Tô<¯ãmƒŽÊË›W•Ô‹¸Ë„DLÓõ¶bß“ˆöÅŒÓ]hÓ×ÁÁF=®}7ž°Tø‡nËCA·ëæ|CÉ„6±}Æ?ÖÄ´( ïdŠH©Ú&—¬çNËÉ?.ö¶;éÝ£®2eO>ã®:[× üî¥Þs)U-_¶å–Å®rf'tœ±¸'9ãö\¼IYcXMW/¼ú4½·÷PÔžÏu=w˜t{›Ò2{6ê˜r~ ­¼v6Ê4À1ûZXãnVó£Tì«kóŸ»Á𫪽9åXÓ?źåp¸QA’™æLP¤é£~ŽìÕDh†g$]”6HLµ’wåÙY3à”ÆÆÑ׫κ>0T,HzW¸­W
HH\Ò/­ŽÖZÓ)Mäº9 z¡L™z‘äªjØýSÔÏ´D#’jªª8Ô­ôÑ8Ký>gš¢Ë{ª§FìÌW3ÓAMÏqv5™O‹M±k"X:‡FWkT[»ýA¯YʦŒC›6÷´9úáîæ¸’t%“Ú΢¿®Dän¨Kéù ¥ÖáŒØ½ä½ä ?ôÜV7´Ÿ t;(è@œvH3ùg•35Wï¢}qªòÑýTÄôqL#ù_Ö«t«m% ?AÞ¡sÂâ\@Æ`0[o˜˜˜à`²´¥¶%,«•n‰%O?ÕÚeK-%3?’リk¯¯¾²‚6<¨ïFñÔ¶¥<ˆóó·.ô Æ žx-”ßyÏ¢ìü¯})úë6Š4,uQ£Q‹êÞ8¨—ë"w–›(ÊTÓ°‘Jç¶Iž#SÀ¨ðè@ØA4„†àA#d^ ˜7[à%ehŒÍ˜Î¤á$˜€š$¿B†%€
ðòLñ¥zúdSæà±IZVÿ@¢ë”ª”iD[V„ª©“úÑbΘb¦õ»áFŒ<„wàëÚ>À*ÑÐá:º"œš®°nÇ¥G±K@¹÷è¶ÔÒ£´¥:ŽþØ”ÿªØÖv\Å
sŠFÄqm4ÀÖÔÅS‚†Ôvíô홸”ô…/á/ êßídG”#t®_®Õ{:VƆ3Ç¢3CñÃOŠÛÓùL‹Ž¢“‰2ö¦. W¹âsÌf|Q¼–áKZ¹Ë $Ðë^þ:×ÿ‰å(܇PW ¹ßÍr+&y fim&@Fia3²¬øE,š9‡S±t¢`JÈ&¬—ŽËJ»' üˆ²1f’òxríḷö2DU¦)6åÆò0e;Y cäÖ„ÅÀÌáÎsxÖU^£*œp*”hBÖ4K£Ë¡Ë'Ft
*;{¹éÖÈDáGCsôò&`hà Զ3Ë)òÉÅEª<H,õš¤5„.±±Ll'´Éåô|«BÌâ`MKFP ‹«¶©.ÞdK~±©R.Ç-<—Ma¨ð!<5ò\ƒ[ 67µj2%"c«3¹l
"2!ÝÓ„¹ì²8à^QiâRÝ‘iá*CcOLºµxÖ- é‰ë´±·“9 fÌaŒS‹£Ò%˜ ŸQ—scZŠŠé%!Á½ÃºÜŸ$ŽÈE#€.Œñ¶ ;¹cúÅ ,Ž+s‚nÀìšá†ªåæ9}ã0W*Y´¼e¬™6›Ðˆ¿d‚W,2õ®X‰÷x‰`2 ·$ôd+Ó Þ’¹Ök‚¨cÙNã°Ñ,‡Z±WiàŠ çá|‰'.Èqk¼“Z„¹KëËõl.¬|VF
º%cÔ¡p|iè®2º½Þ½FRçÐ?4^PeúÍÖæ˜ò†Å12yyÉ*[,ªf™eãÁ.]` úo¢,l96 Ì£•“# ˆ·×“„§ÅrÀjèLnšÚ*-g?^ȶ¤`“i@lÖ+_/ˆf¸óÄÅòMîø./·p8À©Ç^¿ùg/ut×’
%Õùó,! F×õ«I¼écéÐ|¨•@‚שbˆqA±½7ز¨ÄÖÕ7¦V‚>äõ£iHhl´¿SŸ%çGâZjHŠ0fÕ<â®íÖ£n8DƱ½ÑÜ.q[P¢p m*Û<‚‘á¶uÊ~KÚУ”ª•ǨÒê×jh(RˆZ¡ êj Q?¡F°®Ðª~¤ÎQ)Ó–óŒÄ,Í41f…YȬŠÄå…fp¿2N„uVšî†u›PK1C°päB|fØ@d­™ ⽘€û$Œ¦S}NÇ}8á{mJMOju©êÎ^£¡ñDÌ!a¢Æ¸í Â_¹-fþœ1Cû1²„8GD§ïpE{Lî V]a€P½”¡öÂú‰Üód/]GÜwè:Z(I}t:¡,'ÔJm§Z,tˆ,Š¢ãT€rÃɰ¹/lÊõAr…Ñб‘·Áø²ª´Øur!¦CìÀ<™Û&y‚í2…1àyVËɦ2—r0g‹…ÍÙéhÌ ‘`Ÿ8(†ûbžy‰†ÚB?\†Åá¥À òµoš°ÀíóÿÝ[Â¥a·÷cä`Kƒq–P‘+!Ú6 ÑÑñ‘£µ.DaÜ<éüÑí_3lÛ‚žõ5)kc&A¾P4Js±¨¢Ì 3èƒ<ýq­fYA ”…£GXˆãQ#l¡d-ÅrE˜‹?·4:& -À(}æžJø?­ì™ºÈAÔBКâ‹gÚW7Å0òFÊÐ&cÑS bE*T "021|s”Á4T? p¯ou=V«ñ­YÐ".xG'±yƒ#ךYôÑRäVT«Ì°SçXAó]ÅG¥¬¨àj1 g
[.0›ñ²>´©#gÔ)ékŸ %‡°ÙÄö*–¼fÆ<á­, À'ºáÔo™¶ñØ0áw–™(ÚF“ܘ·Ë¶ü]–:¥døàl»|†Æú·¤¦!…f{–ÝaÔ¢ªÎèœt¡·8ájá.Ü-hèj/àçâ´ú`X4Õ¤NŽÜ1LjZΕ˜ €îîíº†lŒŒß¦Á#:ðBz˜F`å× ÐÕâ6 ”¥–‰]ÛµîÎÇ6Ì =ŠÂç ê',Ç»ýDKaA?° %fÄ?®Dó_?Û%ÏÛë Ç’ ·/ÛËÉD”à㑲Y¼ñ
ØqhY°˜¸ç“(ˆ0Ÿ¸üòN<HÆWÒœ?š" +;{²ô„tKb,®ï p<Qý9€]k ŽC 3‡q/yÑázÀ»ßþ6[D^'ÏXrªà»Oõâ*ÉF$èÄŒ©I—†¢Å(r—„lUph5àÐ,É¡ÿ®Cú€¸11ÂkñÏæ¨ôòNgë“‹cäÞÙÉÙT [ì´z©%“]§{¶¥2:ÆÎ?Æÿ&?U¹0QþQe—Ör1äÂ}KZ±€;%Z·_8&˾s¢ëR¾]½,¥×ëŸU#IóŠøB^·zÄ&¦üü•Ìn-* , -´U@ÊG¸NtN¯ÕÖ¨Óïìu‰è5ñ±þîríëÆÉí›õcüeó|wír«Ýdgsýhj½<ï½Ü¬¬w ¬ð•ýÏïO÷_5?Ÿ½½¨¿;Ü­_4™«6z§;«µzýÕö6ïÞw§›Û+ÍãïÊ?Í“M›7ù‡ê‹Õæñà% …Îöôý§Aó¤NFãÍ[µ«(ëÓ%Sí?`¯Ñí­5þ=sº÷ßÚõ·6[s:à­þÈÑ7Þî¿r{ÝúÊmûÞ\¿}±Ú3•­4'O_ïZ×å&ßhRîè[ódÖûÖ<âÊ|£»¹êö*gÚäŪ—¬ÞO˜ÆîäÛm£m6Í/G“¶îtôÆ¿µT:~®uÕÚàWóäÝú­¯ïÓï~­ýêöµþËöÖÁýJk´õÊò}ø‚5÷Åêá}eC=U÷>U:zýÇñIkuwm£ýqóçF³³þ¹×!î?ooÎ_éǪŠgâ—±q:è¾åÚv7˜±òóÈø~®µÍÕwë[lãÎm Fk¿„ÿ¯›Ççúî‹Õýã›oÍ–¥®Ï7Þ\Wó»7F£Qå“ÝSûµÙQ-Ò¨vÏù ¤­±N·»ÛÚ‘Ñ©b¨oíâMå¿ÜWçz²Ê¾/Â(FÅFA•nŠ-j,±€½EïÿÌ J1fïoŸ}~?<À¬Yå]=Z[º¾3,èU0N,²ž®bßÀ/ÅåÉÒâa„³ù ›z†ˆmvƒ²TÐ]2 ºTc
¾…fg^!È¡§*E¿XŸâÆûg(…‚‘¸0búRŒ£w<«°·7¶+— r1$O f±^¬B÷ƒÀ³Y9“rzæÆ§›aÓóõûyÒTð« ‰›@$”L†–¤?£B¢EýK¸iŒ ó^.Nõ©õŒoIk\Ò¢å“üõõê¨i»ÁÔ±6_…º¤5W'î4Lé.Lˆ÷ZcÌ>5ïËcïñ¾¦HëîX\­©hZÛaº"jA˜Ð´:?Xò’ÍÓ®Ì×*Á²"æe›;ÐFìï_Ž‘žë| ƒ²Tò‹K·./|«t¹þ4íYwG|œÝwVM95ñâÂäþÓc6'Ê'÷†B€°Š¢#Â…3zô•Y»cïŽ0"'·ü¾"”€-vœJ•<?.(bXü˜ž(~­á ¶Õ¼#ÖPÄ uU™…â÷â’R72^‰(V¤‚è`…iÊs¡˜æ ˆúRþ¦Ó³¶ÊÓýX÷Ñõ¶ãàý’—á)óÌ%ÌfQ]ߪÎqPa²Œ$TzgÚ"J]¾Gk iäËÇ«?#çRWõmZ‘ýžÍOÂ.̲ X5ÓYJЮuPÁ‰…N •¢“cPX_æ;a»ïøÖòà X”q“ÁQÞcŠ/LxU[¾‚kCŸ@á•¥Q-’öZ €¶‡¥QåÎGá^€ò°õ¦pzæø‰4/»|›};é|¼Úl“ëm˜EÍj@Šu~(¬°ñW
'ý¶ÚÞWƒ_vL@/±k’˜^eæÔµ:ÍÃ)ˆdÿ…Öz‹æóäO¿5P±gž„@^·¾9)‰·S†ŸplAŒ’ITŠO û×1ß¼¶âÖ)"u üØqçYÊgxËžŸÑ›«‰{ZÕt?9µù7ŸÛ+ØɼXáà[–(åÉ ±˜¦rdÈ/æÌYë† ³(Ñ_ø)ÀR(¢‹è“jVb xš1n߈ðŸ`0ãÙ°B'Â’÷2$a1ü”\˜©ž‰Þ->†(ÏÉ‚3•Ïš7ÒˆjSG*™æòH†Ì4 †JÑõBµÿ¥3K
Ñ]åG¡èÓä×¼I1o£;ixŽÌÈYP"r¤žâ^O7LšOS
ûà:à}§£ž¸ö¿pănRŒ;,‹72áä„#ƒ¬²>3Srþ‰ÜS[²ïCeôfr4„:?°•ÿ·Àø5, qÅ´Ê
•§`¡É?Áʶ¢Ì¹Ã‘±l†Äµg >dª `Æ@ìÁL„IÊÉ"çÔF4%[Y"Ùò–ëY+j9D*iSúY<Œ E·ÜGlÿ9Ô/r„"â|‹±g™f¦æ? CÃ%fÕé¨RI2PCoüÐ ¤<|ô£_ouÊ{…{]´À¤¯Åì ƒá÷ WêeRí¥w0º´+yïöa/˜\¤ ×õ´uq5sòlsò­Ì6N$.õß×ûØwÑ:v1çBƒ•ôp gŒ~õbàÓêqŽm‹ðpW?#.¯j_*—O‚Ý a¼àÕ½T㚣óL°[nG:¥àhwH½Û1ž(‡ÇÓèK`–ÐÎQž…ZQ
Ŷ½‘¦º´Êk¶‚“RéÊXÀܦ°ÏdͶºÙ‡°šÂÝ–Ù*ÍÏ~0øˆƒkáC´ª~òP/0)i=Ñÿ7Ëɬ&pRú—Ëɬ&·©ÏŒÐ04mjÌ¿^šÑèNcðÉ·ÄÑÆÚP’ÛȹpóAÛÜæ£’€ÆX '‹Õÿ×fAŸWÞÉ}Ÿ*|JezS¡Á B^Ð8 Ký'û.‚Â>K•#RqMÍwƒŸßèζÒâkí2wFpZŠü¶Txf)*h&@^ØRúMÿ.Ìšÿ*º¨Iù ¶éÕ·Ð%|™2 žŽ¿;ÙV~YRØW£Âäùæ–ÃÁmýDÎÔ\-ƵÝÌmSm¬M®Áïyëo<âòÓ
Çwï#ÀÜÈ}aSÅÁ"R×âf‚¤àb06±ŒÛ:0ƒHÍw[º©dÛÏjJPXù3br¡•Ñ0èQäýde cvI#²BÞè IÏ*%¡Qç@·èíÞoŠ^{ßxov)0¿t×o7Œ>‚Þ\§u¢rŒlóßµÅùAò-+dÿ¯%»03$î|^¶ã©j7‡¤‰\z·;‘бhã];
!~2Ôþv;|0ñLº6ZñSdÅ£]Lqç—RA BqP®º`¿ Í÷ ;©Š2¸—ÐÒ7ŽŽö\·ûRK¼¹ÍY£8·UçZ%X†¦•LÍR…°T:'FŒýŒ·œ5(Íüu0û³ìu@c›¿o’F”¦½6E`ö‚U𠉫ïr_ T¢$ŸÄŽÌfQm‰–ïѰYޱ|¼úȶáûž½o`|)oIŠLºÁ̯ _î‘sÙòû\šï˜Ð=øü/’öš#$±à1šQøàÎF"d¡B-
*—H-&¾Å §B=*l#* ˜6÷hìpa†Ä÷vr((¯»¹“0ò›kiƨº”H0EØstyí¯œ3 ªØª¯¿nýº¢ƒI‰îVMgï6(iöM^È£Út f“4,8Z@žáÄ'÷3 PþºIc¸‰ópl®í“Š»Õ@Ht>"|#X×yµ…ƒÍ"凴°!çgû¨b4”LT**A8l¨ˆTg¶ùLùdËçm*c¯'i/a›ô†¤’?àGÙûh’б ˆ æ%­y›Ë'Bã ‰ªß2¤4{{ƒ¾:Ž…×ñ^.¦ä
p±î¦.E‘œhEÂŽ£W7QäÙ ³¿B•SÕµ¦¥V¯*éB¼"õ
Ág¦9è¸ KƒÏ¿"êé÷Yºö@’^ŒQ_Êß©w=½’ÿCy•.§ÊeÑ'È;HœgETT†Nq@‰Sâ Ί"Vuõ~öÞˆQ¼ñ~Õý'Eà¸÷ÙÓZk‡´A 1cÛIqXܯynóƒ0¿ºÈy.©­,!S {E¾Îw
ôéÌ0ŸÅ¼Ž}l>µ ÈÌɦo¦ð¤·{|sg–¬ò¢˜Ï*IæÄS‰F»äCߊ8rbhÑžhüúÝ•„ÆN4ØC¾¬½ê!³.fÓ5´ù1][Dcl##T™,w´+ïÛÍh£‡Ó65Têh‘žD…jxþÈgz£{eàýQŸÕVdì¹Yîréúï÷ãòp©BÙÖ%ùI¼jô–d6•n]eã-¤30²·Û–œ"Ï—"Ï,[%W.VfÓÑ-ûúØ0¹<idØnHì|g2]uÉPG}̯ãìo/°Ð®µ‚.j®þ×H-™÷l….îjû`>U,¿œÙþ½P¥pf<'Awl<Lg!_l¶ódå‚"-%Ìdê›­MœÃ™|<ûÑöœlãzbÍí•Ûû=¥ß»‹®ÔÑä'ÕjžŽRã‚J£ó…f¢@û]îOèÅþ‰Û7òÉßý"½}m¦RÊÆ|€s 7H59Š²Ë¾ØÛ&°|NŠn«2ºsd …¦G^4lXséçË´³'ò‹ñŸÛþbŠƒLÖ—%±DWo³ÆãÉí
ón$Fš$¤2æÁùn6+úÊJ×\.¿MŒè!•™«Â%øX¯‡ßÜ8ðù©¤ží‚**nØFŒ#X¹SØ2tþè´Ïâ '%jË"~ ÈügB`“áªdEì·—ë¡ëEGb\~ÉIïâÇA˜'à9ñ¿H–—Mµk"Ô ßd5vá{í2_¥þfÂê _fjA˜Tö`ÿÓ¤>µ½U—¿5~À‰ªC™Ü€!–,æ}=‰˜Òñôs8¤™‚Ž´©ØgvìaîÝßÔE0WTlÙÖ㺮' b!(p1ñ¢• /hý)Ȥ°¿`툵’çv»ãÄyûW=f£í™0Ãi¯]Áß,’Öv7UÙÖbå„mÑ„¥!е)ªG|?Š‘´¼|¥º=Me¥F“m_>㿉"©›îÏ"¯r{`š)Njîô?Àv†nñ5åÃym9@˜çvx?•Ö¢sa¦•¯šÓvNöt‡ÂtR~7a$öÊ3x¹únê=VFëÔ/²ú1Y*
¬OŒûE×þüiC·dǤäöôææë‚´¼-dyÁ÷»FôŽ· ½d/K…Ê<“~ JIÁë0g>¾½5F7zߢ€’)Ž ~¬^ ž_,öÉ#Ìä#·ÛkÔ3¾.þ_›Ð09¶á
a2¥¡jµÙ¦_;=«¦çF¿ôpï?í?”|kPõr‹¯£óÀä).[u¡o$²ž
C—øÈÓáo'¨q_•£`E#S”~·×$¼¸8F=¹o”ÑPY Ž>1Ýt÷>Y‰î¬a@S6Bm—¿ö ê‚ÌÌ–*ž .Lm\˜éXî r‰­¢Ø.e‚¼H­°ØgÈ‚G<IÝÖ58’Ä84¾Ë!£(sùzîÍÍ1 CýI‡Û…cr±?>N ¾búN„?øsǬ;\YHõ³^ä)–.ÂR¹Íöà2½TQ¥×fÀË£¿óds’'‚]¾×dþÈ
sÉŸ³eñv¤Ñ…Ê7°7 ˜ïÜö(÷½Øk½À;Ô%qªuákðTp™ëÜM´<[¼ªV†'‹¬ª]®f7»ñÜNå2Eã¤Ìþøjö˜®HV–o¹«ræläÐ’àKwÝje‚DЬK<Ve•ÛhÞ’Õf2ÛEË$ùžÞ2&õ¾ï^'2f£Œ¿eþ^ȯ~e§]mƒ¢ÂÆ-
TË~¥å›³ji
I]öÁ‹ &ZRH¹0“êÈ?70”ì:±-(tð˜Ýˆã<û•Qö»âUÓC]ž(Xc¡% Ú9·8®,­n¯¾ª &z¼I²ó9ìÐÓÅB}^mVR”¤µßܨ‚-ð§F{Ø €s¸‹è×ËÅW&¨T!WÑÎÞ÷í0úCFÂüò¾†ÍBÍ9÷÷ø3€¿bƒ… ͯʷ0/x’Ðc‹å CñìÃsv±+.EVâûÐ^ÃÌíÝ¥¹aåOªèuâxÇ/°='¸„Ñ6¶½H<Œ{h¸â÷ëy¯vh>ÝߣòÜç³bÉ“]ŠödKGY° åÜ€U$|ϱö³âÔS»Q>HvÑSü8Y'¶Š£2Q#×WW±f.‰ñˆ·ÒÙ ½ C‡”ƒ`xÊ9sÁ­gN¥1ôÒ˜·õ '.â?ù7÷8ëð±åXÜQÑfŽÆÑXk‡³M©ß‚¯©TŠpDËãÅ©öúߪuÊÇ*ÚLu4‘èhêêY5"m¦YÇüŽhÛÐׇå õéþf!ëˆÖ5£¥Î5]QëLE9=
Òæ—½z0Ìk™/c±1§.×Yý—ñóí-j¾·¿qdQ¶Í—ËT]E5­óÉI*XèÌ¢x4X ƒ`1ó)ž ¥ qÿ ÝŸ®²DA68؋ے³•›š Cþþ5̵Èæ'J9,õ¶ÞÜX0¿¥1}ÆB«5|š,"Xð’ic¡Zaa¼Ç£¹¾ïê
è—çø¹—CÛD¡1ÉEP0”ûžˆ®<¾â¥±Ê¿¹u=Ÿ›±¡c½ÂTÓç<U¢»Q$:‚þ=ÀÑ@ìËbŽÍÍc6u¸y!\ÿ_h¦&½§ë ,Ô•±0¦°à<Ò6£¢Ìw+IÌ¿Þî0¿Á­ÍÐüöСM´[wÏVà·X&îÝ›Û<y¼U#×OXiå´sÝzºýðã ×@à ·Mýyļ¦þ=ýJDˆ꿬WéZâ@|@¹sp« \á>EA¹W\3ðþ;“ 3tÿäS&™ê®î®î>xÑIõä¨d£…Cw4?H¹O¦›1^{/wv}W¦ 5™¨ßzÞƒ”wƒÏx…Êš´ßgtPÀ†âbGíúÛ}›šž„›cŸ€Ñ—£o<É×|0F?/ð #³Îõº;Œü뉳ÙS@Õ¸—| €†ÎèýW ìö¸ÜKªµÑ2•ÛÄéÚŸJç+'9Öe"([,+$zÛ»Þ/¡A] “ϦÞD:´gÁ‡ÁõYTã΄ZgY$¦Á—ˆôWÅ5Éê °*ïåþ®ÿ:QrÞu†ÚŸ¿ï¥J¾Z¬XUP¢rÜ#ãAë‘A°3+{q 2›èò8P `C3›šJ8_aT»\.ÆU± TîwÄÙ\
5(—ë÷s
(ÔdÔ×°ƒ^¬*i蘓¼£W<¨Ä=³Ó»HÕtl·wx“š¯]ßR4Éjð±§‚f{‹œ ôý+G|<æ§k¥Rm4€lP_ËïcY|æ Wq÷c(@Qa ÛøÓ|WmbA[÷¬H-M["Gýðq­¡ Lñx_KùiõãÝu‡}eW¿‰ ­YCS_¢h°÷ZÚ<àAË"ÝÎ==<âA7EŠú:”¨½Ò÷±¾Ö¸·d)ƒ­$²ÃA£ßÇ‚ök‹ª h°ónè3GíE¹¾üׇ­Î7ËZ,*bA‡Ðd¢¯;ºå±@»m.ûgYÄ‚F«>ÊþÔ÷eh|«€‚.¦«ÒaÑ…ë½<v+ ›¸Ë›=-s#wü‚zÏ:Íp¾µÑ _¾ØÇ*…òšyÔ@÷Iâ©4z—
šêò³zwr;a‡ ~øbT¥‚ïèkz€zîóTXMò%Ÿ 44ón‚Õ‘*:ãé’Ô •<YAXõu÷<]ó·hGõ74§4ÐxÃÐK­§£µ½MÇÃ02]„òmf°ŽëçÈi·Á/_I§38{p§šòéÍ0„¯A OšÖNçÓrŽòãÝò"Šž®fTÇFÏ#õB¼G<
îæˆ|:a,ìx˜÷_į˞UR ŸNj_÷¸S…±°£Fýšä _GóLþ¡-«§_ì6†ÜÝöü¹Ÿk§<GO'ÅÖêÈæü5ã9N™˜Ó7z°“O{™ø£~zÆØˆDœÄ¯çûú&O<]¼
©îTcl¹È%¤¯»ûñ´(âé¯Õ¤U&3ÆØ™BÏM<Í>W&SâiQHR<™±g»àtÅ _‡ò\öÑ}ô9~wœz^rR;MûЪ̿Œ$÷3î\©Y·3y‡q§É÷§šñ¬f†Í0ís§á Ã~¤õå̸šå†¡ «™°w‚$[¤í¾b¬ 6ßù |øáo9»7ÓLÃǺ˜NãÚ2¨*Œ†7n¦Žº¹£ö±îÓºO;DÂ!Â/.Õe¬f†j
T¢+¬‘Ôd°îį‡ž#ðçqâÚFeáºã3‰µÔó ünG{ó÷ÃØ÷uXØn߉  Íy"(—ûª¶p Z½À†ù ç+ý4‚[Nh´šj@?Æ¡ƒÊŽÙ´ièû"Bpbë\~A¥¿&PW—
è}p Ú4®ì'_E„`¸;  Ý>ÙS*'M}ß쫲;Aáî0Ã>Øý8PeS`Ë,`e"!‚ÂyäÅÓiì¯ü¥E [é÷Ïåå7Õ÷‡åêê{aÇvíÔŒ
Cx34ÓòNÓ‹ð¹÷¬|sV¸£»%d§yÊ} ÿ¹ÃN@KëIÓdýÃÀGÜ«?›»O0:HvFU%HjbãzØœn¼S.HÄòιbC"&}d€l³Y<n|YZ{x+k @ º5ÀµÇúÊñá$z©Àlš¡O¦y³÷J¼ž(’ÑÝQ1y4¹]ÿ2p™;¸ì¨3¿&Ç'û!J1PˆC4­<`J¾]3i+àL²ÑšQà R·âØ$W€ŒÞîièÉ¢©’N:žrøÐL®m4ÎCàk·àŸò¸¿Ø^¯GÐqòÏMô¾mbü@¯´A¿5’¡WÉÒ®‚ÓÅùe¼;5Ï]»ÌJ²ßûN}ÿÇ™%õ¶—y·YgŒFï߬²„Ô{¬øM²ÌÒãÒ¥GëûÀžAÖZñ»ÑÈö
;#à>µ##©Ë(âCàn=ÑK°Æ›õª;~EªÊ®Ï\•FÙÆWee ®u‘ª<Eã×ÄäëKå2Ñ0.R‰v7 ¦d<V¼šÖýŒý³dÏ*(¯ë;WÁŽÁ†Sšfäb€y !LÞ ?v²ùkH ¢hÄpDb³µçñ˜;§Lžˆ.Ç<rÕq³x™¹îÜç-œÃUðåöºSÃÅH:xo›÷ÂLJæ ð«›Á$PÆ»l‰Ðc8á¹1ÎD—Õ Ö®„4qNLçOÜtßœMaBâ9ɧDìq6š(>øœ^Deâ@@œO½Ò_0Í|ì/6&4¾¤èJ‘¼y‚E“åééS2ë4Ú–ljêê÷‚Qûd`R­©YC0 2f6Š<ÒBg)~1'2óŸ —g~SüV—:5~`³A©¾üI2:2¼¬µú_™ej'ªŽýà²îå%æ6Æ®Ìu·16Üý7ÆŒŠö Æ6ÆDEvg‹ë>б‹Ý2çoYKufv«‹ ¾*÷Iê¿Ueª³u^·F›ù›*;îçIËN’/•­²ƒ³ÆF ©.Åü„|jËDMÖg
üh˜¿.
W ë+Ù * ÆÅ
'æÙò6CÔé"½ì‘ÍÐCèš¿óÜWÚÿ¤Š¾‹ávÃøÖàšš@Ç…¾<ñCª'k
0㳸\û  ¸Ær&A;q†ÁŠ,¸½žÅ²   ä²Iåe‚ÅeYú{úr[¦ 9vY
·êdÌè!{;éxÊM“¶¿ YKšåçn*/Þ,øÁÌžÏöeÉRA*ñ«³,~ãÙ?ÿYgnèû7’…)qb2(™l"Ë\â¢3¶˜K¼ˆ–8vI3d[dc£ñÑàK²çâä¦u?CÒé" Ú^‰·ÇòR±,šçwÇ.v%Ñøû×Tå/z’ñFåÕ#£4^‚!ѹ-‹æ^©ç¼Tbˆȃ­ðM‡ ™\<몧24å9£Š)LÜOuZ0]ì*1æLD~¶„ì„-°c‚1µ—£ôÓª¥y˜y`¿ €0J—ôQšT/¸‹7èËÖèmÐF_n„b²a¯gÄ•6¨ÖK m„ßrÍEtÍF[T¥ÒþnÊs=“7%´ó}Ë!»¹å‰†ŽÜ°,É6º]Tå7cnuxb®Ï²€›S£»˜ÉÖfYn:²Ü'û‘6x~s!ßwë. ¬ñ¯-dÄqãÃ{Ýiéýî'ÝN™ÆÁe`à+]M/øÐ:iì€W—Þ­š ‹FL¾¾T®i²)Sµ™±éYÍxå}T»Ø·ïÁT îu‚½pi›ü®5ÿX¯Ò­Ä™(øya@ 8Ž0ŠauDqø`\ òþ_w¶îÝéáOŽÇ£Õw«ºu%%üæc‚éµÛr‡ï.`ŽT8ÿ˜TӤ߄ä;dK†Í¹¨ö‹*üØŠþgT§Òð%i|…_7Ô»‚éyQ„^)gp¿0‹‰—³ŸŠfÎ3kŠfγgw‹*(Ìñ‚Ó¾›ö£(ôc$ØÖ­ms§Tm >D-û o[P4«ûHÓ¢à+š$ŽAÑ$ኦã8j-í ÐaW£2‡l2Þ+#·ñçžÖ¨t—²«^bTßÎ[¿{I=5PâPaEx”KS«øšZtfÐÓ-«x~¬ŒÂ" f¬q?ˆEvõn\£8 Êöü‚Gãpp7¼•&ûû^?%·QcflCÐý¡h4¶ã£èY½VÒ·¤OƘõä~0‚†>(I çïA~Yÿ6¿Ë3G߆OñK3òÅ7¼ÙŽ¿·¡È]Èœ1‘mxCó÷´‘” ¹_„÷÷kzº&)Ñ·!Ä Þ…b[ÌÂô÷A[̉ìï!
mW6Ù†÷‹ ®íBø
gŽ—ëÛüŽÜ†Á»ö…¹ +é“ÓÚô#{pªý ̾" h ^J†À94¤qq?»‡¡œ®Ý&»‡BìæÊ¶S1!ŠóKÐÉ<åÞ$ûÀ¾Ôs ‰M¼,A N\äZ¹zåÖ“‘g‡êHÓkו¤°ÓqaòÕ×´ÿäŠj}x-1ò:3.xñ… •g“TªõQñCß™€ý^ÕNys?ØîJRJ‰§g[b#ÿ= bÔ^>Å¿ZŒ^ŽEœ¾ åQN2Nö‘uäM„PHöD w!D/T,›¸hŸU¿uß©—…Çý3s¹ÔÍ¿±ÜûÅ8¦±ÜÏÒC,×)7b@b øÓ(¦Ng×𓇿kÆÔú¥?7ùÒY9áµ3é ØÛÈãe¸—Ë^îM—fQYâ[L.¥À?vÞòš¹ßÞë•2ª±ÖÝO´Ãáj¹<ˆ'Á{¯Þ{2,j;ñ†¤,Wù>1Ä£ÙýïãÑ=Yîþ»£?º;ºEÚj‰=kÆ›³"óÑBóõ×óÑd±3þC{äž}ÁsݽJ`îÿª]a¾$“2zÔ”§dJ¾òV‰·;ä“[ÿˆ\3#棠¼‡:óÑB³\øE{TR¬\—oúƒ›«ïÑÑov¦ñ¦¹Ë~´¥>¬G+×rÝ“S“ô÷)ýуX>èѳíQP1{~ûÕ£À{rìøwîšèê¤ì>oýä4"cܾ¼Ñþ(¿ï/Voï\Ä=yñОsÿîÛÔš;¨cŽ™¬¬ïZÿã£.°*é·5ƒÙ\-ÙÀ[~ÖÇÝ•ÕV,é)1mATËíÄÌUÕ»:é`õõUÖý@*)¬]ÏV{©&óì¥Ül; WwW‡¯àw '¨€žKmfHc 1g˜s妉/žlW÷^»bR—½{Â>9Ð"°÷>”ÏÿrëÄr¿wu´ëmVn\§£ÊE7 $·JÞŒ±ëôc‡X|νŸöÜuÀ+Ö\Nf“Ávæ©01ç7>(ÿý"8?r¡ò¯0ƒïH èüÀ®Œ\t'$#·1_H­É¬kÍCƒÔŠm†3ÆØ|¹Å½!3Ik6XßHý
6¥kç!­X÷ƒÍ—‚W\P¼Ï¥ÚÎÏç55‘‹–HÍ»øP1G9YÀo&½|u·zo(+èÇp¿²JÚYч
ôÜG¾À„XWÒHý$Wã°i&1K¾Ö¦ºàùC8C$Û¨a]mnšOGa\Â?×Lij¾à͹˜&?5ß½ôv>—ⴠ‡|½TŠÚÕ§x·È6IøçÄeu¿²`D¦/ôÚm¹COR­:vUçã#‹WÌqzçîF®Î÷?ìÝ((ðßýå†v‚ŸÔ1¶í:¿¤D›f0õ¥Å4]’²¿óýo#­K«§cDAðϱàbÁü4nHå¸ûxP>3¬÷ཀྵÌ
éØjp°‚ôëýº H1û+Ft°t4¸îÙfáþ-=[Åö–!Àx7FC•Q°âNmÖÜ0I¨–$ïÐO²ø”Hµ·U1Ri#VŒT´Ð³ª;hzm8ÊÁÑýå qÅË:ʰ_V‚d§îx0e«…¤ˆAÌãœõ‡"ü<Ò¸ívß*Ì¶Ž½¿mtì¹Þ’yì1!xò°àS˜¿íÂäiÜê4Ý=ÁQ¹z÷•«˜`“ÇÑ‹‡Åý¯ªÉå>¯ž´ŽuW²Ë1æxb½9¾…âùAB\ž» Q|$á"+¦å"‰Š¥ë_aÎLÖ$ÏN|ušä Séúȶ¨êØN‰j!õßæsŸ‡òc'0—-1~vâc<͉Õé¨rÑWg·Jäea×iªmgÐQ&9LP”t$E$(
C…än1Ôî–êôjäïf=(Guœ±É[8Õr;1sÞ:¤3+qg¬q?oRf¬—z ÔKœûLÒ{†&'œà]ªb½”¨ä“ˆÑ /ˆE.§ïsHŸŽß¡³R“_rdjéàÔìϤã¦ï
PãVïMRp¥¢g•´³¢è¹G>fBµ[DB©ã¥5·>­Ó$sKJ)Q^ìn£0ä%Ê"¡NËå²ìêø(Gm7d}#“f¹ttýU Íò©ß,û2ËžZÂ!?êgåˆÃðyJ%/¡¯°dR"ÞF©UÒ‘S³ì.Æý &¢²’‚'ö|ð9!ðÊkj1 /³dbèR¥•E-Ìž@aX~¿’`užM~uìÝU/±jC´Ì+9Éð·¬U ¼y'ý÷ ¿—ëlu^> T4þª—sÚñcT0Ÿé¤…€é^ÁWÄö{Æ`µÉ[s£ƒ·fÆ y
;~ÌÂ)îÔfÍ(R`£PV©–‚8 ¤¡¬¹q ‡¦Þk8h—<—2̬㑯– 0Òj¦ÀШirïA¡8\ºÊÏà¹è¡ÁŒ¯`ÉDÓó¡vÊžÒ—ÙdÇ*&¯”ðCšš…€n-úò’º3ðWÌy–g–ñc‡Åø>²î®»Ø„ñƒÞ,º>BR8NÂû½ª†(àî‡
)ÿࣔøqzûæ¼D«¶«0bFÕøc'¼Ý•
ŽÕ6[(9„bÝ•›âÑ¢Àû%G#pês„£M?²‡îñ ú1@’†:Þxµ–vöëaW¡!p^;>Ú~ŒÓA }$§áèS”†>º÷‹,* ”EÂhnV[äZä¸q˜»:G<Æ^R N.: 
>’GÛ0ÌÁZ8…(Öž„*úç4ìÇŒjýIµÜNÌ<怄&¯›é\4F¹4}¼ŠqáéØS|¸
ô&Â6ÌØS|çßF"6@=Ë4(З§Ë ¼!»áÏõÙŠp¿€¶³ñЀ(æü&°û‚8@\†¢ÑØ¢gõZIßÇ9¿ðˆYvê­go|¶«{cq Õò«¯iÿÉU†9
‡>ú‡NI”†•4[€¶˜³¹i[l¼ ¸¶p PJä¼|Âì‹­5);J7N+f ü€a`™°âéoî`D*V:\w£ß• “y‡/B` Aõ°…M„$”àDP¡}¿ÖÐÈçü÷øg—CgWÂÖŽ˜ÖÐ'h¡07²^NcH
°¾Øb€KøðJiä©gž$@‘¬K²À¾‰l_øq ‚r¯Wlo;âIvÅÐÖ”ùò:Ùê½ñ·¹,)c=<›¾¥·ár°. +¨û+¨cÑ倨[Кc½Æ}ÑéÁxb,—Õóiëßài\—_Vf9qüм®öÇÚWÊh^}Õo›£ëúmcyü³½wÝ1jù?†QÓN³êêÓ]FÊ?+`ÌÁþ²ªÊ¥øËÎ[^3÷Ûrÿóß
Dëî¿§
Úápµ\Äîò¥³×¯Æ2Ì4|T¼aî_TzhR­-¶ŒmèýÏz¹í&aø xEh+±U¤´Š¦ áXT´^h±©¦˜ÐVÅCxÿ²ew­˜Þ/6ûÍ ÿìüSÚCÙ~(Bï½2×*ød¨4 ·/îl Z5FꀕY®ÿÙÓ ³Š”ŒçZ4+ uyžM¡»}SØÿBë™òJcÏR¨¹L¡ ¸x s•^J)tÌj·T¨ÐRVC
TfÙ­LÓ×bQ®–w%R¡UÃHP†‹°¼µð\R®º C{ƒ1ª÷ìbÙ—ÖuåI)l×ý‹uÞ$|}òI嬥©uú¢NÞØ‡“ñè´d‚M×–Ùqêòíõ‰
·$Â Ä—*ÜÁ
ktžª6ïcãhOeþ˜ØÉûëhµ²úe€‰†ýļ‡ mK²ùíé Î 3<"NI xàõé·A˜†Zäá8už¨î÷0+iþ×ÑÄ·@}ÎQ'É' DÔûöUáê„_§äªSfÐC«±]3–Á‡º ›a®_¨§ÌU‘41ÌÉæt~ÈÃgH
ÀÜ×¢Õ%ÑI!‰êk£ûåG€Î6Ä endstream endobj 80 0 obj <</Filter[/FlateDecode]/Length 15065>>stream
H‰¬WÙVâJý‚|„@0$ ­Ü&Ø@‚´ˆ‚ Ø ¼^ieþÿ[•±*T%øR‹¥°Ï¼Ï>b±;IgÒbó^TUU©¹HÿW˜<‰ê¹þWÖóªX|ßfÏÏÊñ»ÜÏû¼"–›Š¨ê³­ºŒME@qþ[J¿Ï*aE(4Ób9™hs|n“Ü8ê2þséü&nXQ¯jð©2W¯ô?Zf*ŒµL:÷…ˆY‘ÑCŽÇ}ÔDµŸÔ/åŠw#53>\ÛV:­LmåÁfjã^þöÞrÓµ×—¶0‚²ý[ý/ˆÅ«Û¥LýœçÓÿ…ó’ñ˜éPõ³¨®ûeàh÷ ͘Ÿ›1Þy@ÆÌà¿&!ýàorãöÄÐ’a`ΚùÌZ©¨U5‚#ªÞúôŠ3BŠq9ÀeÓDj>ÛÌŸN f¬/-eíu£ûôŽñè¨Ñ—”2UÒÔ´pü®i‰¹Î̶´Õ9N'C[ÖÉúds$„Z¢AŽä%€=†dg¢¬ü›¡˜¹¿NP*9®:_1+€[1çW“ÌDz';t ±ô'|8Þ¾– «:5e*Xú}!öýʯ<~ê;ÉRJÏWur£I¥ä>Éz¬tˆhÆrR)±:¿\Ò{).+Ð)ðëÎÚŸae©#Kðy1.÷+„^ÔaZªhÌ£zÑ`£dwm‚·1J}§:>aŠuA,ôúÎ/wXI*ó#©•ãó“3Æuä×ró«Õ%ñ‡Á)`ï=LséÚ)·~p^ôð=¬ JÃÀZ¿l±ûØ„ÌVAßú­ :¦Bq‚a{ŸÙ/G]``r¢ôQýž Žÿ®ŒÉãˆPû®Œá\{bÆpF;0HsÚ¦”zý$ˆ´îo§‰kpT4ß&NâΚ«~2Fœ«RoºsçÊädºö¤@¼³²… `M%1@Œ”?2|F¾ì\1­kKObLñkÚâ”éþ·–üÓ¸õ+bsÁñ¦L B@‚ð‡ðÈÌÆÃŽ¥`ƒ*MVO°U¾*d*XZ‘‡™ ýbKò„mÕ Ãì¦ãMõ5 =Ã-€­·=åâveМcç¦#q—|@SóÇ?“ûàÇMà"°< ¸’n¼`áP´#ÈØëf4ìhús búÁœ‡}ÔÅA’GyÝò¼¯KÈ5éf‰¨.n‚ÕKž€K¢`|ÑüÇ婲õ´€Þ]œR:[óC§¥/69>. Vó—§@€ºÄñ~Náæñ¥ƒó²+5¤Lü"*Êõ{í•\-={ß1@öñÇ’ì£Ü`º+g­T”ÎÉux,Ü ¿vfúPIþY÷f,»—1ç H¿Ü¨¡Y ãG­˜¿oú„fê‘xÔRÚ¼Èõ>Ä”¨ôF”ºÃAÍW
) Ž'U˧Vz‰v÷^çD5ÙÒ)ÔÉX¡¼«üĸiÁg6‹°%–—p'Ù8-; ŒíJlŸ/QÄÑõ:ÕF½Íߟùp%ümÙ/x“/8yÓ ÅŽÌãûeŸd©#KðyñÝ WtS+É1Šîe݇…fu2®?¼C=ŽÊ›à1æ5—¥êá9÷‹Ÿc¥YÚà6öv%pÉ»+™ó–= .”9Ž€æxjvpezqõ+=`,¿¨Êào±Oßf`ZtªÞúôÞ/GÌlªd ÁÊñ º‚¥YÚ>$½‰öËœÚceƒùNî±Þƒ‘ïƒFØé1/.Y ÅZyt¶dÄÙ›@
Çâà','ììe𘆫ՃÀp©©ê_ž½w%ø«gOïËXM2«å/Ê(‚¾³•ÒA
Ïð–…ÌìÍeÎ>1™0•Œ©DI‚„p¦rO_R¯j‘ÇJòæ+–± ³ŸŒ‘ÈÄ2Û̸ˆaâÁT¥Çlv2«’€`·^úH <Â¥„ƒµÎNýŽ«ßíº.T! Qå„…ãƒp6ó§“d3DVÞ„Uïdï¨>| k£zÑÆ¹»wÚG"‘ç²0C±©8…¿Î̶´±] -¨äã¢U}| »ß3†° )w­Ëâˆ1Ôw[™ã¡e…>†Ú Ë09Þ1ô@+X?têvÝ1<^Ã-Î:†ü®4pNC€‚!˜—cqüÆÐƒBQã&} ÉÒž
¦œz9ÌÐv ÀøÄÌTx)£“#‡’ “˜C Z{=ÁùÔOæ±Áê™ãé1F:GÉœÁ*ù§q‹¨ñcNïsV¹¦’ÊTãPS:]v”TPõqn(Žç¤óÉfK€#c8•¹‹“~_ˆ»‹œÕ2ÉÆiK*¥ç«ºË¹xI%0…¦Ÿ4ÓæcÒÞæc)OàØ]yÀöüÆèÃw7RÕª˜Ädx¨©4ô˜¦Ä50ý,J®K> ʘûX`-A ±L¥¡u2öÅÓ] j©·>YÁ(Ä5\S÷¬a%Hîµ B¦Ž°…!ÇûJÃá>ë. ¡‚%•¶œ’©¥Å¨À](Ô<ÙÀX"B¼PP*`H%r~uã«Ç†;‹ÑÁŠ1­(_¢æe¡âÈÅ·&7nƒÀX4 ÌX¶Ø}l­`&.§ÒŽó^(Žß£,¥ÔÒ—Œÿr°2æqб#ü–CNjÛe¸ýc!%Á–~©ôæ¼Vx¬Ìår)]ª„gõ‡JQ|x,,Fâø¤·®Ó[¡\í ªSEPCsñÔFa˘ý9 g5ζ¹ü]l£F‡ëp¾• í‹÷M¸Ûo!ùTÃY`×?K—55_~é»”n^~ €ùÓ…Þn½ÎÅ{©ìíÛO’Ñõn”—©F3ÕL¢I1zŽühÇ_ÑëÕ0kí¨F7¡ê2K7ª‡Ÿ;£ó[f…›Iê_Û¨þ5úó,ŽÍ=DÐô¶ÎÄè4~D׿eqg¶ëù{<V|ùK6š‹¿Ò†ªã”Ç(Ç£ nülQŒ^„ùÜïʈb´?ô1ªG.°yÁcÍè÷ágŠÑË÷_sL3Úöåx¬]ºQMëTñªFà%ç“ù½õ.QH{¾gflÿ›Ùr†13Jä1|d,¿ØjîšQ÷sæo\^±? „ÕYö9ŸìEÇñÔÊ£VARÏã7; ZñîfïMjïfŽw·3‰Ñva‰* ±©Äñ´MÑ¥ æU¢æ¯xU/Í©@—Êk’K¦•’%¶2ÿ±$o¾VtC½@¨J—¬a€S·»Ãò”ø5Ñ˯bìuKÁž˜§‡¯K&ðäi/ueOo\1éi1´/¾Nî'ª»p¡àÛ5>Y{Ýè ñÙ—…S¸ò>Ê%h8…³ÄaIÇ\šl¾i^Êë®Ô »dyóÜ9ÀЇ®´šÀéJ;ü” _¯ûf|‹EÍö"+ë?ë½F0vFrÒOž1ܬÆc…Ö
Ãhƒ~èäÐ\oN%Eéë¨U5¢#‘%ªY+…û…,ò5ÏüPæMXõ0¶$Uñ¨Aa”2IKŸÁN ǘR'WäSÀ¬¼[a$<*ĸ^ÇÕÌTk>Q31LUTçW7¤
VÌù¬Ë𬥠ã*Î0”þ„•›ÜÂTãûU(+?…œ}®_"Õ/PpH¾Píî^:²ä<æ÷äqh«;ˆKìPÌ­ ½6êÒürÍñ‡+R/‚¨zDÒ‡»æx’·Àh¿B¨¯¬ÛˆÕuê‚äqªãƒFXjŒÔ:¿2©Ô…ž,{ùµÜTWJÏWuÊRcPã˜SajýÜÒQë8­à<â¯$˜ëWÌß7ÝúA†a« )Y™ÙxØah¨-À>fÝÃ;Ë…2˜ßÛÌŸNC àVù¶Œ½É¡þ·eì-{þ¶Œyí¤ osjÆîÚ®¥³J©×Oâê°ö?ëÕ¹–ºDŸ€‡@$´$$€ ô^¤+‚åˆJÿ;;=¹i¨ò…$ÌžºÖÃeVÁ/v6"°½2_ÒdÜ5˜ÊÜhã¼Þ„ÊÀVw+S©ñyø…F—¹é`Wüi¼X7©/}ÐEØÞ8E¿‚ﻫ7ìl4Úò&¬ÁE¬3ˆE,ªf±Ø0­+4SÈ8a^ ©EŸΩ“Ý•Ò+!–È¡H‡A2fŠ}¦Òj!M 2¡IU@W‹µS볡6á½ùTjõ”8¡XÔú)ÎVêI)ƒ6ü¨Ê¥³ìd.R_vQÔ‰´Š$yÅFýŠíO·%§§³ë×Z€G˜êõ a˜'=Èà{ìÊ<©ŠIŸ‹Y‚ºèçiCþM pbBµWZ;eäRØÜ%‡}§lãÁÿ]R±8ÅþQžÔBW)ÙÍSÔD;sÞˆ°­¬¾zÏ iö\ðá«öÃ-—Çd¹MkÚ=H¹Ê“Cx¸ÉÑ…Ñ4]ÇTA?•u¹jÅc°DÈ£i EPpK—®ˆÉXCž5~®Ù^¥6Cã/ë6ò[Ÿ± Çåö—yT.$—A_BÍ5ËÀÕ!uA×.jÿïp—rÝc(',~` Óâp_%••Û%ͬ_Î šuv4Â_7;hpõTZ°3éÞ™â»È•p_×Jvíö;“îû]š˜§ÈQ›6þ.åÄŠþ"„KKzLÓ䉎×N‹£‹9ÞAh \ä}³‚Z„&‹nÃþtXuhºï¦;¸uî”Áî×QoÞÃȰsn[X”è8éWÌ[ùáà¢S„ÄDm$FAšÍÃâd§“uç—=±®Â"ÂH­úåžé“¨NímÒº®’zûËèÑŸÚÆ«¦(àE¤bÆ ÚL!ÕªÒcúˆnC·"cflg¯ÇP¾m ŸU¡\þ)ÉpVh_î£dZ}v®¦<Ä•:v~1²»ùy1°#sÉ‚!Œ©VXC´40¦–š^h›Î1ŸbrVž¤9Kó›šÃm¾«‰Æòg»
OOß) ‡®ä.ÃT~¬}r*Më‚.Ö"©5Ðë)¯<å´õË«w1ý5Åg4ñ}»ÒÝá6›øÁîz%a -9cÓK®n5 в;C¿ì®Þøí쟶Ìõ2@»%å¡ï|¿•œ•€UõmÚ Úõé1;¶ÆÇÒR³%©öÜÍΟ»ÈÑ`@ÔˆV÷X ‡ üšl’vÆ´—O;†ð,`µ¿ØÃÝ1Ô ÷3†¼)iíâ˜áŽ÷zcx¥Gù6Þí6ðKñ¹àÿ}Âãñ±½½rv~:>JCvÈ?‰ŠÒ !œò#iÏ£ 'ZcJÀ1µ±½²æD€û$¯`Òä€egÑTJûdÈ@§Œƒ¸~{Ñ7À×›ÑÅb{å…Ž ™Æf7†¦nBGeláº~ …ê«jˆvÈæõH¥Âƒç
ÌËO휶C»ÞH»˜Ž—ÑÛØÅ¬í$ð‰²çµ´hå¡£ÐÉò‚è7¢4 ‹“²ðþfX*|†ZŸû$ùÏ•èM4Ÿ1ÍuÆptÕj†P…0:e8†* ³˜Š¹•©œå]K‡Å”2ÀF*!Èm¼j¤×áVÃ¥ñbݲU<¼îF¦ã©ct½ae ÕÀ:cLzÐkþÅ^ • IÎÛØ+ ò90f»ÂÛ_§äž0í{Â0
ÊÂP:%²à›Âp¬' 9„Q†P mœF ¸8¬–]Òb-’ ¦ÊNQ]ò¡ˆöEÁXÛë5ò+ª1õc Îo<Ö¬éâ*‰öJø€Z–Û_vÌç⧘ž²#¬mÌÑžKÐcÖô C¤9L Éz \îs£Ë p” tÕH•7ä^W•x¶ò¾ †™7Š#LVg””“FLÓO|N -áøä^¹‚dŽÂÕ,6íäÄPäÃ:*¹¥ÚE§ã1|Ú8VjNÿS £ÈÑ6Æ’ìF7N¬™ª†(üÛI.Ÿö,sÜÔJ±Ïi¼Ó÷áÁ/›;ä¨Ê{+ë:\rQçó늻y»½»=͇C7rØŽŸb‘»¼m–F¡zèèŒ4Êì<×ÝöÓ«Çft¸é­[‘Ã_¿aÃ,Á>nzݺýM|Þvë4þ}˜Åîcæ;Úr[i/Æàå¨ûí©Z%.×»{^'¸Ò†ÈS¤’ê÷Îÿ%ã ¿þ{Æ›¡J¹zŽ*½-kT9êéó8~<Ýa^N®—èâY<µ)29º,Ñó'Éà0Ä4â7\¨bñƒ<žžwwè™KCd}Äùp7iÌâÙeg`òq/Õe¶Î/sÚíËf5½dqé€pÏžã‘Áœ¦‘nðWïºíLz)ìûHB]<aöýâú\9W©Dþ©Bü¦ç9/=ۚÛßðǨ˙-õzn
[¯àY{§ì{Íø@hí¼Ôh .„æD\‰‚“aM/©¨;)LJëQÁµºóÓrwmåW7ò|_˧Õ])îÚ¶‹Ã óiûN•?£g‡;[‰:`gpͺ?95Cû·1/Ôjü’¹k8\TÙ¯]ã”­‡3q0ª ;·ý ä?w-—ëøn|ä6'Š<ã!ø9;;D'#!¯xGã0Yrû†Ÿ1À©îág2Àý{:ÂÏ|Hü[‰,ÃÍ[0[¡ü…€ûRÂ+Õ*ðnf·èhP¾ŠŸ_¥~勆*_¼ÄÖÒ RùbŸWœB+^•sP½‘–Où¬[å‹wv%½€•.ŸtËPˆ,1ï·hÀ¢Ý–®¤» üì(m/Þý(·ÿÉ‹%ô(Dí•£—Á$>œn{¾èeºŠÒÖŒ.;}þ”ØvÝ»P¾ý09GŽ˜°0d‘úï)&Ò™;tÊ“Ïл/Û^¾5
õªóQnH®°§rD÷ÿíU\˜¨”¨gß_Àb§‘”,æ"ç6¶.O'·ïÙÇ ö¯8Yµ)-÷oxzyÉäÿ½’4EŽ_±½z¬"ðB:Æ·
—±aKa¸ÐÉ#’bãÁöbJ ;ZP½‘!Ç;tÇHw,o …ŸÁÞlYBužQyG&”t§è¶ð<üòOl  £|QØbÅåö§ÈíGDŒeåq…"°[*üùè.âÄ êdj€q gÿ≗x&EpœŠLCò¡Œ‡x@IÂe<ÄÑ-Ý!u¡ø²ù΢§¬Ž?Ò|0Îü·D©TlnN=E¨õ"b°ûûyiF<M4ôÍk ”Ÿ]ç/
EÄZMÓ1o³ ³Åaî0´ôüñ{/âæ> /šµÂåµý§Ä#Ét)ŽuWùiÍUI°¯FØ›º)ˆîòŠ«} ˜þòƒýzS#¸1?Pc‡PŽQ²žOè-ª¾ØœÏð väЀ2BÀ%¹W¡å#ËÙFËÎA(|$DZˆ Þ,è±À@€ãu;vÏ‹å·~âIê1é¿zÉ<k«^ „%u ÕÏ£ªèðTðT>ˆ<DzlzZlsŸ7ß%5®B³Ä•ü|ºÏ XN¨8MN¨Avnéÿx¯òþò
ò"‘… ªÖ$"´õµµt±WJíÞÿ33„P$üžú©;sÏ=÷ÜsרA-Àê~×ZŠ% ÊèVP” J—èSîâ?ŒSô£ ÐAWìKÒbWè;„3Þ·ˆéÝb‡' ÎÕù=|Lç“øºeÐ…×Àôxý‘P ëG:^ˆ¶Öºœª¦ìp£·€“‚Iôç°ÿžs€žÏYÀ‚…6‹çamkÜØ&Àâ%4Œñ&C ™†:ÛZH,ub¦ÛÉ ÅñÌ×kTåEzŒ}{”Ûzø*ŸÇ!ÆS*¿µ 3¬c#çë“vý0}hz ºçð¼ç™8ÉÍ_Ô6sJůx^zTB.T>Ë‹
hRŠÿ+óyâÕ‰íD@éSöN.+Ÿî©3L+o SïµÞKw<‡B‡[ â¤\€
P‰ÔdgÂ×D­Ç!?¶Õ,ÿBËÅ’ØT•««ŽÕ“7žŒö—ßgëÕ²×öбF¿­ð•Î<ý™¿€n yPšúd{å¶Í±dµí͵ÌÞ3X-}ÿ"c
©%³³»A9Œ„Ñ|EŒf>òÐã)gr¥ Î$Ÿ¤Üþ ©*#AÞôbHÀVY¢®¤øPNú±^échC&[Ã;›äÊ Ì&N–R?™øq¢A…±’ºÍÄQ¿Ä’îMêpû}Ýwx³2꥜qÀΦy$£yÀ$‡÷ðý’ÏÀÒ"ÔZ¯Ì.µ=Aqè×~ôï)t)מI0GÔêi¿ÙÍ“ Ÿ]ûk÷‰[µ½|™N¸Ø7Ýì¶æ"Ú¶vã»ÍJºˆh%‚„U¬˜D%ÜÞ¯i·Ï s†>y/k(f5—b¶ÛÌörãgʺ Q‡³<Å­Ïðò_z XÛSÌRºH-mAm)]<’ð¢ÉÔ99CŽÎwÖa_Í1_³åÖtÍ5Å›VË· þVóíw©vi™÷àêöRØ©_”z傪¤˜«;‰D)åU]6¥\¸S?§±Ì¼ùV¼½µ\xÏ}ó¥KîÐ]Ü8¯’»/¶úJ²2š×TÊ›Ým °Lž5ü^ÉçB´õ°a’aç"à ^ûÒcíiû¸™aö O׳\%Ÿév)>óúîècäu5 'Í4r¸Ì
·{9›=ÙÝ8t³ª„?Ýj˜³¼ÈgÜì¡h¾èŽ}
(<‚ŠÞÄØÝ°báPèžtbÏ=!…9Ó8špOÄÅü(#ÈßþÉ<Þfцö Ïxq§tÖ%ÈËøc÷€N±º'˜ß'CkYa­5X}ý®KºÕlæúÉŸYø îP-M!x|ÝRÎpîÛš*¹d4]¿ÓÚ,Q9„<ÍyÓùù}…¹ Mr6m¸¾ü×u´Yúw‡GX„â2Î~ŒÇ¹eNlž‡¼è[¦î¥ÜU•‡ã¦öýjÔ=Q¢Šó=ìÿOæËq€ÊoVp­¬«rK×âÉЕúi:-©qCᯩÅn´š¬ÿßù¢Ÿlë-ÉÔ–ÒÅ
3uéZ®ÚöflŸüGÎGæ ”unÌEF{“Í`¨›âXìÞ¥4”l61„ºœYª´Y …<ŒÕùjâxŠ]yã“ òìÆ6Á6‚´Ô^6Óµ cgqÌR{Ùl®¿tÌl‘Í5—EŽÙl.¨0GÚ+9õ¼¿0É· ½?Þkw±c•Jp>ª˜W˜QHlûý=R€í7ÓãáÛ²#Oá"\ÐÏ`k­ úÏ àîn^í•Yô¶èÁ( ¸q¹Lr˜BÀ¸y²(˜ð5ÈóÛòuzŸxu:Xýs-‰“‘‡±c&-Ùª昞ýgnªÎE†¼ö¥ÇÚÓ|Ç@‚<!ð—pÑeä“­8J{K BÌh‹ºÌµv½¼eÆC&ÿé¨/·Ä¤ç/pòšñy¡UÂðx<§ÙOooSø¶6C&éÉíuà]j—AZæý¸¦¼$vê¥^¹ *)æêNbÇÓ¹ú˜
=ã…D½q¬®ñ®rlS!Hû»
åI@61d`¿l>Ç>1ñAø å*Ã…­»´ÁsúX¦û×Í]!ÇŽ¨Ûꎃ>ý<ÕÝöˆ}²Ú޼ª4ê~›Ptü ÆaÔH}ÔŒue—.äØé„ͦû &¾G»SL²™î|ÕÉ_Û×1Ëéäeêkœ.Dì"õ5Níb—¨¯qºy™ú§»©Ëé|¬ºóßÑWyËKÔ×8]¸ñýYß4çMCÉ;7+Bˆ­ ±®è
¯žý]KµÛiðªÛz÷~š­'‘–® “|ñ|Áztæ{9W¦u…} A¢­6Ÿqí(¼ý,8õs÷"÷º‹âïÀÒ³Ø.…¥_BÞ,¶K¸Lè”Äd/<iãܿݤ xn^‚»üáwa' e×Öy÷Ü¿È>]ˆ˜úœb×Ê7HV3ðSiâ°Ñ$^Í´ïj3í»—¹oP3AW<ÞQÛtý>˜ªîw³í•8ëÝ@lfì§‚|9@/žÑ çúElT-苬®6+媓àm]¾ïŸ¼]­q÷Ki‚_ÑžT}´Q—^× x]ÂüžW°+A7¾c½â?¿¯øá@·yÙ+Î?Z¢nFtd ˜/õ-§ûž7W¥!t¡,¾Ïù„é‚–‚ÀWy Áë˜ º„adª u qÐÑ@ùv Û wÀ ÀLÞ!Ž5¼ä>܄͡‚MI ß„ÄRcÿ
ì@¡EÝm†©hi¹!¶Lßo!È5yÇw÷>çþäÿ£Û¦4£ÂT2€ÜÅ÷‡çœþÅ6ëbⓚyMt„AÁÁh[’Í4>£ÖDƒ®ô¦yî¼4ò!^SK»iT£¾?Iu`=Å~‡h‰~³!ŽªKõ=»ÞƈYìnõiKò@¿BÜ,½ç(ŒÀ¦Iæ À1³ÕØ<ýƒ>€þ8T]ˆªÀªF7^Ø…ÖGBD #6AšOã+á³Ù\¸ú(Ä4í?‰„AoœëVX>G6'kÂNo}œí!–•gôæ?Õoa÷Ÿd¨ tôÿ—wœ&ˆ>+<¼T¶êaÎQ!Ûâ ¢qÌ,M³_¡“ù}‡}Ħѣ1FÎ%úŽA 9f6§ÚUc¹p^8«ãs©JàäDž'}Â6D.´ä«q]ˆ·y–êÀSÞ¾æ'î0Ž«Îö!Ñs]sg1¢Ç%xm"Û®^8mFuŒ|r/u›Ò?抢þ±\•ô¯uYg0iÕ×%@s¡˜þ}8×?¦Ä„þ1§&õå|JÿøZõ½gIÏä=jÉ›ÍñJð`¯ ’…ÑÖÐ\„½’bÚÏʪ›Í§›wÐe"{
!rÒÏ$¿îŸžæâ}W!èo’æúþÇz•®'®#Ñ'àÚ€ÁÆ/@Øw³{¡Ù!$„°}ßý5Ï>U’¼t÷tÏýfî#©D©TªstT[xüR¶ú$`K ™¿U„Z½®•gÉtém©h(›?Ëëý)¡*£}PŸ×6²ª,OAåukxÁíkÿ Œ9îù¡;DœÂÇGÝãéû4ßшHàÈòGè&%ÒÕý™Þ‰DK»ÓËV&-ûÝ…Rd—O5¹3ÄÝp† ú`A $c€ÓTâWÖd§a¨m,CÈiØe^,ƒê0Àój¯ò ’½â½±4§?HŽé@ækËt¾’*Žà²eâ
ºß2êÄxFìÏxËwW¢“δnÐzºx¡Ž…è
úéªRN0ÎXç¤KÝbakê¶‚Wɸ:Yiõ÷H ™Ø±’­Ä BtUôÆUýÐçËrBœåS꣗Ԏùöáðöé÷ †>ú)½È%ìí¢2öOøQ^4ÅsIKÃÔù.;Œ<àâIižú«ÕñD·ªa—’.Þ6{s“O޲ÐZ­dÝx®k´Äu;JZ€}ã0ÔÙèm2ÃKzY\ÎYKßEÑE”º˜?çÑã3|VúQŸš*ýïüÖc¾âèÕŒæY'ѸxÏ"µy·L©ü8ªá^V޽,ž¸¾!SáÜàÚ+B»@¡³~i:ׇ¤Þç¨’ßødA*/%\ÔG>púðC>=Â$aþVßš‹NŠiŸ>ŒhþÔ$ÊZÆòÍš?
?Eãâ íÒ`Z1îu¡7øî/¯‚ù' ¸f™CQï£<¯*ׯ»JYoÅqUæQôC«W|”hZŸìoímÀ<¦ÀIPɕ۰ð\Te¬ôT^R"Ù.çx$5à߬2vt)x>*ºœH@G™†V@Ü‚WŒ–BÙDµ=²ÊTÔÊÓK  ÃŽI{øYŽ 0ÔU0Œº0&ú؇²Âç ·!ɘ6› PN7ö 1jpã>}Taz%d•ø4ìMìŃû³bÓ%+De~`¬Ù—|-<ãäj%¹[©Œ“«ME®9ÆT¸<0•í›ñ
UÕPá_YW\»‹‹ëjü›Ò»¶çöùm÷öùMv¥\J¡®iÃÏͱrÞnÛ¿®Æq}ûØ~^¿%¿)…~©^Gíú¸ÙÂt<ëEÌ"» «©¥cu„-C×jéVù—Du_óô2KãEg-«.ezw¯œÿkpâä×÷š‹ç¤uˆçäùfÂÉz¥Ï µ‹ŒÝq(ÜLp°Ã/žÒ.oÒªã.à1¶çâ­,µšO?áŽ\ЉŠšqñå§â¶—W.»trÚi%Œg#÷šÊiñPQüðõKÓúÁ(OÒª6ö‰§Õ¥‹"¯¨ÿ¤d®,| õ‹%c´Ñ]º@Ë“6KGór>ëW"\Pñ?Âföç5â
'‹ù<ŽÝsR×ý€)x@CŽ ÂNnj\àä9ãEgX\u‰2‡W ”´«ÛÊ|nÖDçh¦ês·ËBëñÄn½;DéÛø¸ªªRHQ)°Öû6€ñô $ý' $ÖM
AIÒmF˜‹—aòʘÐvM˜ðЉ¶ÄïâMSFr˜–ÞRÆ4”l5Õ‹ÏÜi¨+Nð¥vÓÐÖ,ÃŒÜlm™€=êÌ]µ$; Ž•«õwXâ;QJ"@óý†žFº ¶8èvòÔ3p-¸˜Ã"€äN…ˆ¨{[$Õ¨¼eV(«›qménÃÑvÚ
œ¼Ð·z8Jg¶ô$ß“ö U"j.ô÷kÓNÌÂ\… â9h(Ë´/¹ûô+éräÝÂ'HÍhõ`ò¡E”&[²²×› P@K÷géôhÊfâO¼wñ*Ü´yÿv`Ô7Žái<ÒÌúèq æSX,#ÖÆ—ÎòZµ‰‚• „¡;THÙãé¯Fª¶jÖEh£·×j¦guÎjµ ›­M„¸`G»ïðó%Ž÷#óÂÜOT³5Ó§¿Òý¹f–ų߄­@WúŒ Ï™jhx¶—ýÇá•p1ާ¶3ñ¥ôzWiqOÜA™û@B°dûƒQd¿Ä´IHב}'­Y©\gªVÕò´–šÙÚØòlÍ®›ë{„DãâñD¯•€˜MŃîVeÑâTªøæ—˜z_•PqMï˜>©KÍ|±Ú¨yß© ª-:%&+õfÒT¦¿Þ ìŽL+7¼#Fœü8+)>8ñáY`¼yKvPkÀ¥¹²\ð³û¹4W‡—8ÅÓ—Z>
;²Ì˜’€¦@Þ]SÙô¿Øx^Aw|†G0YŠ–ùÕR$Pö×ì ÙR²ØõŒƒ-ÇÇ{^å¹){E•B2¤º#ëÓ[º®¼®!*ß䤔ý6à±ò=ÁúPCîñ¡ñËé£i«Äο+°ªrCfòe¨á^ -cµÒb_ØüÀªMVaš£¡9ßNÅÓήÂ#`íO¤ÏYZ|À¥tléž»)ƒÞõN¥Q-XÑг}×r=ä¹.¸íøýömO¶N®VS¢ˆÈ=‹W¢?WŽÙ¥'îâíò#j@ÊôËçsfísjŠù¶t>§d²^¬"°Ê¥e;ðœîn-ÓÁ‚'àÞ·]ÜV”_üF‹Ø÷¬ßc´mGas™1 2Ò»‚>¸ aòKÚ©"ãŽÓAôX!LuAFÃõ‰í¢O]˜È¢¯V•¹_?Š1Të‚VíT-"llï–PyFá\Ì—Îæ‡ÌãKw}[$ñï?0¯üS` @*`!QÈMvîèC—Ò©¢23ºÁ^Å)bÕMM©Ãðu£aüCbZ‚}.#aîòù\OÈ“„,*¬¼½ïP|‘*D£…\üš1t43v㨶$©Œ\&>̇‹ŽÐà±£q¾!ÍäÃv
\|€Os 3 ÅNƳ·“@°Ï’`\úV
¾;Sp›´­ŒìP“ÿFÂù® °$,síR¼°°E9ûHH
IBN»¿°ê/Š + µiÈý>üu€o`Vıöž%A¿iÎ$¬Û­ßÕŸ°*‹[ßuÿs!ÿ{Žjü“‹ß:@Ý&’y°—ÿÑEù{6àLÿŸð@ÏåKwUú¶á,Hpð;TþÑpû¿Ù¯ò¯6’#œ¬wãƒÌ®w7öîË&Ù#Ð9êsº[`Ð-yÁزÅmâ0 cãø—üëIõÌHI€ÅßóðMwWWUõUuw¬W3F SÔë"fÀ‰y7ÞA)í±3ÊáåÙYé;Ãý¼<kv»
|¬lSàx¬'-^ž·Âý¼ô«è5A?+»x¹ßŽ¢SE‹—'Gc<±[k(wP*hü¼<©‚=°l9UE‹—§¸ò´-Ï_-ÑDöÅZCÅ|­]2Ô"Ê¿L7ÑÎ/wÊõƱS¬
ù9vVϺ ı^S%ÔâØqirì43:jß™ jçØ¹ÌhqìDž†c¡“8Ö3†žväþáìÒsO9ZiWîµòùvär§Š&/ω¡}G>'Â~V~ÇîÈáÓyùù¾øŽ&éy<ék.×S­&Ld¦}ÍñR±èÛÅ"ËÙŒ¯¹14Ÿm5ÁËk9_3·´›÷5+õB«I†Ç‚­¦Î?²H;²ˆ‹ .1T}[է艨ÿZD޶ûcSj¡?&c ý‘uk¢?:ŠêÃimf¬äH±ÿl‰–wU$E»µÇ;ggT­'¥snMí¿+é{Ίï&ZÜ_ʯ†§!ó<¾]ÍB–¯:×Z8Õ¦õ½réâ€q,ç¬7Üü‚kJþ¬r´ÖͰ{sÚÄQ× ·™±,¦7-Þ¼u-ÑñWéÌN%ŽuôÇX° tmƒ gáÖέ$FÉhk€ ÇyË7°üa%ÙZ¥ˆZC-(^H8[KÈ¿r!iùοr!k €K‡ÐWŒ£)èU
3H§)DZíסY"Z7”J]y*€{Z$êÝ^g'cÞÕeõCعÉ@_ÑrD`2\gfç^:.ñÙ²«
\ˆœ­ªÎZpÏ›Ÿtb «dðˆ·èüŒå¬BÓ’Ê‘êJh3³mçgÒ{ò‰ÔÌ!ÞPvcx1«8Ê´]­×ØAhôÀÃ8??“ÆãôíK¨¥Ñ.=MdR¥¡‘7™m1}z™ª½ÖdW$½^qùZ/ï8ôzI›ôzãîZ1jš!«@²[ûÐ;giE!»³!÷«2ð*æ|Ó_„ݯô;•F'//°k[ÜÉxöP|·d]1ÄM:÷·èNݯÊ`橇g·BšhÖ`Òxc`‹åsä™NÜÞ´e ªW~úÞÀ€½3Ùxet(±U.åGsìmGî©Bù9,j¼† +嬮!ϵGý\¬/Æ5BͶTØýÊ-MEÜÜ·ü¸ë[Ôéƒɵð8hì…ý‰½ìfàèert¼ÌRÓeôD9T“çA‡Ÿth'0”[ß}¯œb«TÓNËîº[«W%¦ö]vÄ!ÂÎDpꔡHCäÕ{½“ZºRJåÖ©fq¦€¬äؽ*@\©§Ð!Ü]4¾`Žn´™¾w@Áh2ßyºB‹¾:t [£nÅÞ,%= Í—;°’\Ò¶8•³ùÒC8:¼­«ütÈW£ªÑ}&äe€4 Ç2sùnzo –ÇÑQmP|ZŸaÆÄ«‰Ì›ÝÔFG Ó™óLÇW€w$v}W(%¤áÖ ÌþÃ-̸°Å¸nZnsb0ÏN¾CNír
¡ ƒ#[©†îf {©¹,z÷ß1c²jãÕ\mãeõ?õì»õûÕZÝ çEµþá@ ðÕtuk§V¬|ªØtÿüé·P&&Ò$œCƒëÞâštdM2‹5™©œX§ëÙõúλZåð“™Ð]ågÅÒdÖL˜î„U˜0bZi
ø«sU«ÿòGýQ5Þ»?ÚZ¾ŸYH0.m¤(#\pkqÉ­8Gˆ
*ô(E¤„ޤ1DÀ$R&©$¢Š`nK®õ .%¡œ(†±Ð³0ãÈ'f+ò¼ ?Ac
>ÞB×G#󙹸ŒÌ mМC–ˆ„
©h&ÔÄÓ-XGqɹ0÷ &-IRB0!Ý2ÅnÊ,°ƒRC™cVë’Úë SÑØ4ˆI56XI/?~‰þÙsƊƶñÚ¨Ï eCfù5L¦à1„l¡¨·S“p›ÙˆCt‚ˆäLÚJj
d”…mˆ647)¶lj3¬ˆ²‰ ‹ƒ„cÂŒ2Jl€¡11–$»%Ö *-@ä žª[†QíUŒ ²„›„Y3 @mGiÇ0
*• :°ÅmŽ…’„én³ €í:F1¶m¥ºÍè’3àè®À}
+¡”<FJ…!šŠA/ &À}
¦HBL¨¡¦°XDê·„™1Àzì€×mеÁLée1¦˜+ÿã¢+áà°OàÐMœfnÏé܆¨C¢7(Á6…  Ì¢C–ë,…|TÄÖ 4âN2Z
¬Œbp'W#ËnË[­Èƒ uÇâæG7Y=à’c'R5cð)¸™Qu&ˆÔ…ÁÀ»ÔnR¤`]ÁN¤® EH
¨Y'SW@:FK=(
Ü‚x“½X töuIœH`¿ŒÇa¤y+¹2»xìÓ®b¶¦R—=]-s*)EH£Åã0ƒ… …DƒÃ<+ \¬Éa $f‡±R ?…ÃHqA±[ül³æ08aDøI2mT(GPï>ž«J3»3ð±3JFcKö6øÙ0ìfòø6£¤¢ƒˆÂ6v¿Šú‹©w=¬?÷|'æ#6SÞ)!¿³WMø s•£:œ@j~Áˆg«ÿÞY¯ÎÒfùc±y†ˆé È\693©Ð·Ô-WÀ}KðŽ›#5X À±ÅY7[©W&‡ˆåÉÿûúÜîçO§>·ØÍâ<¶kÆynl×òâØ®ä%»tW„íR@^5¶‹ ¼yÍÀ΄ð¦°õò¦¡yÏmÆæ>_"›FÔù|Exñç+‹?_b.oÎSÞœŸEx³P{‡wS8Ïð:¡^Þ5à¼xW
õÒ^.Î+‚w)P¯ÛEp^3¼Þ¡Þ °¯Ïm{Λ|Wðü¹ùœeÊ7w¾ÕÏ;ßô8Ï™ò—{÷ûúúÜ¿û]Oóœ9w~ôËãG?ýÐwOÏûüœïîõýðó¯ÿì žüþÛ£‡}w¿=mš6 ýõ᣿›CD#Vhà·¿÷Nžæšsßøññ?SO“Éäø¨@ÁßÒ4ÚÝßÿôë¿ÃÈMfò““…\z”GúùþîcfýŸÝj}jêˆâS+hÄ-¸Š hrÙ÷-ļtJ§#ßFiËLJ[¤ãøß÷wnnB’› Öíw{ïî9û;ß9{É4rçf~a¹ ý¥µF³ÙlÔ«;›®Í\k»“ƒ;wî?*ê°QÞ­ÖÍ—ÉhÖwŸÛÇ÷¦s—¿€A¤slzöîƒÕ5óôy¹R­§*©Ú†\šc½`wn;Â=+m“i/{G³V…ù©«]Ï:Ù¹‰H“;[;/ê:ʆXœ™èš®LLÂDz}³×>°jÉ>šcc—:PߎOLå£rg»R«Q¡Q+ûÇw&Ç/u¡r“³Ñªð›åÊ ;=£¾½þäîä•®ÖØµ©Û‹ólk·:
¨­UèÕJ°Vâ0§T+ôZ˜ø5·°\T: SjV·ìJžu´’^#-=ß!ŠÊ¦zxkb¬?_ ìl›æ›µíõâB¥ÒòHK
ä@ú›õʦ^ΰþ›”ïßݘ½·¸ºF€»=€P*ùÂÂk™
ëxj&aVJ¥V)…øÁ,R`iíS¿˜ YßÜÚ©Tkµj¥¼áâÅüäÕËÃUÊ~4€;÷—‹Ü,•·JA=y0÷}îSƒóóWciœ3¢°<k27vno;¼½°¸¼²²¼8?7}zÍù± 8=3›ÏÏÎL±Üø'•Îs×»>qõb:©Ç0.S—ÿ¬kªŸuý?ÆÒÒÎñ»íƒÓ¶ÿ‚-•ªæ Þ÷ÞŸž°—léÍZéätûèíéÑÇ'£u,‰hmïð ­´Å"HD¯NŽ~=:ÆâOoZ‡«QrOñc<*ýÉŠ*ÂßïÌš4+/˜[é~ƒýÆ^³cö# ÑÊj´ÿš”Ù_íéï¾·žéã1wÚxËƒÒ %Nç±ñšÛ` çÊ)¯±‚ô?ýƒ0V°Ç¥Þ*¥½–XñÁs¤0Ö[é Ã÷R´Ž´„6¼G,Ú?`E; Kå|ÐP*±¦7¨ãqð^ûØËÀƒsÚy.]V¦‘•Q:†S‡Zƒ%CQË¢e¤Z²©Á~aE-åFÑÜ YyBÑ4·ÒýlnD´¿ÇÂÁ¹uAÄTEæ[m%—²æçBÄímðdM€Lˆ…E
¸³x &R"¶Êjd°ÒjG‰7\h¥•´‘’d¹tB/ ™•xË”Ì䬕ъb+8Ò&M$u,…ö0Ô&‡l ƒ-Ê va@@ÄÆá‚—š–£Œ
amY72pCÄ* |A"ƒ€*gr4V5$•Ú!|‘çZ(cTäb©`–ôôßr•™¦ö­‘4äÄZ%ÈaV%LHù°ìº bÿœ!ÒG€sŠz
YGJ
«P^ˆ·Fs¥J˜JŤ¥j¨¸i ^x­ÂijÛ¾¢¤ƒÒ= $²Ñ~ x&úä3æz#°/}èX.S„Y«êj!¹öèT)uÑ4¢«l—º®Ó#©kÑa¼CCM]‡2EÎ9rJ›zèoaá¦Ë^á¸À¾ŒÄH÷ʤæÄ[oB”±$e…J[¢RÆŸŒÄòJq’rX%ä:戬Cºt—à 
Nè”Ã"ÇGâÏá0Æ!)öŒŸýi&#x\piFÉôQ¡ÁÞ£ß}ø‚^ItBg££h¢º4µ’½¬ÆÏ`)ü!®GûY2½c¶[Bíd¯ˆ‚ŒŠÈ+ ¬h?5èIºÄ6Ú¦ÇVäùWíÈ÷ëUûEW-ʉl£©A“¢D$S‹µ?•J.º€É
™ô>ÜI
!o=}< ¨ ˜GI+ë6C%hE÷´P.“–Ù7’x£îl°Ês+à!¬èÒ)ÜÜI}S9 -:­Ð¢½´¸a2"¬ˆ–1Ð…w
5è$بŒPëöü»H'Ÿ3E9x¸r‰½L´‰·:q·®=Aò L#+C©ßÈ,ÖEd†`e9Cž€‚d+z<™cÒ>¥Ò>¥’åÿ¼é|ý†üú ùùßÿ0[Lñ] endstream endobj 81 0 obj <</Filter[/FlateDecode]/Length 89>>stream
H‰ŠçõqâUuô4wÍKñI¬L-ÒÕåUU HLO )JÌÌI-âM/N,KUHÌËË/I,I-Ê(¤¥—ä¥*gä—ƒD€Z`ÊUU]ýÝx N[ endstream endobj 34 0 obj <</Intent 50 0 R/Name(Layer 1)/Type/OCG/Usage 51 0 R>> endobj 50 0 obj [/View/Design] endobj 51 0 obj <</CreatorInfo<</Creator(Adobe Illustrator 20.1)/Subtype/Artwork>>>> endobj 59 0 obj [58 0 R] endobj 82 0 obj <</CreationDate(D:20161110192421Z)/ModDate(D:20161110113144-08'00')/Producer(Mac OS X 10.12.1 Quartz PDFContext)>> endobj xref 0 83 0000000001 65535 f
0000000004 00000 f
0000007884 00000 n
0000007813 00000 n
0000000005 00000 f
0000000006 00000 f
0000000007 00000 f
0000000008 00000 f
0000000009 00000 f
0000000010 00000 f
0000000011 00000 f
0000000012 00000 f
0000000013 00000 f
0000000014 00000 f
0000000015 00000 f
0000000016 00000 f
0000000017 00000 f
0000000018 00000 f
0000000019 00000 f
0000000020 00000 f
0000000021 00000 f
0000000022 00000 f
0000000023 00000 f
0000000024 00000 f
0000000025 00000 f
0000000026 00000 f
0000000027 00000 f
0000000028 00000 f
0000000029 00000 f
0000000031 00000 f
0000000016 00000 n
0000000032 00000 f
0000000000 00000 f
0000000163 00000 n
0000047447 00000 n
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000047518 00000 n
0000047549 00000 n
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000000000 00000 f
0000011968 00000 n
0000047634 00000 n
0000008313 00000 n
0000008826 00000 n
0000012268 00000 n
0000010808 00000 n
0000012155 00000 n
0000011855 00000 n
0000009635 00000 n
0000010485 00000 n
0000008891 00000 n
0000009074 00000 n
0000009122 00000 n
0000011793 00000 n
0000010854 00000 n
0000011739 00000 n
0000012039 00000 n
0000012070 00000 n
0000012342 00000 n
0000012550 00000 n
0000013847 00000 n
0000015978 00000 n
0000032150 00000 n
0000047288 00000 n
0000047659 00000 n
trailer <</Size 83/Root 30 0 R/Info 82 0 R/ID[<CD5C36639B6F20C848F3EC7BF58CC6EC><A2437F556EC84E2C92BFF11F475C196F>]>> startxref 47790 %%EOF
@@ -1,21 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "iCNLandoltC.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

@@ -1,21 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "orangeGrayCircle.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

-55
View File
@@ -1,55 +0,0 @@
/*
Copyright (c) 2015, Ramsundar Shandilya.
Copyright (c) 2016, Ricardo Sánchez-Sáez.
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.
*/
@import UIKit;
#import <ResearchKit/ORKGraphChartView.h>
/**
The `ORKBarGraphChartView` class presents the data provided by an object conforming to the
`ORKValueStackGraphChartViewDataSource` protocol as a bar graph.
By default, the primary plot is colored by the `tintColor`, and any additional plots are colored
using the `referenceLineColor` property. You can customize the plot colors by implementing the
`-graphChartView:colorForPlotIndex:` method in the data source.
*/
ORK_CLASS_AVAILABLE
@interface ORKBarGraphChartView : ORKGraphChartView
/**
The data source responsible for providing the data required to populate the graph chart view.
See the `ORKValueStackGraphChartViewDataSource` protocol.
*/
@property (nonatomic, weak) id <ORKValueStackGraphChartViewDataSource> dataSource;
@end
-290
View File
@@ -1,290 +0,0 @@
/*
Copyright (c) 2015, Ramsundar Shandilya.
Copyright (c) 2016, Ricardo Sánchez-Sáez.
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.
*/
#import "ORKBarGraphChartView.h"
#import "ORKGraphChartView_Internal.h"
#import "ORKHelpers_Internal.h"
#if TARGET_INTERFACE_BUILDER
@interface ORKIBBarGraphChartViewDataSource : ORKIBGraphChartViewDataSource <ORKValueStackGraphChartViewDataSource>
+ (instancetype)sharedInstance;
@end
@implementation ORKIBBarGraphChartViewDataSource
+ (instancetype)sharedInstance {
static id sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self class] new];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
self.plotPoints = @[
@[
[[ORKValueStack alloc] initWithStackedValues:@[ @4, @6 ]],
[[ORKValueStack alloc] initWithStackedValues:@[ @2, @4, @4 ]],
[[ORKValueStack alloc] initWithStackedValues:@[ @2, @6, @3, @6 ]],
[[ORKValueStack alloc] initWithStackedValues:@[ @3, @8, @10, @12 ]],
[[ORKValueStack alloc] initWithStackedValues:@[ @5, @10, @12, @8 ]],
[[ORKValueStack alloc] initWithStackedValues:@[ @8, @13, @18 ]],
],
@[
[[ORKValueStack alloc] initWithStackedValues:@[ @14 ]],
[[ORKValueStack alloc] initWithStackedValues:@[ @6, @6 ]],
[[ORKValueStack alloc] initWithStackedValues:@[ @3, @10, @12 ]],
[[ORKValueStack alloc] initWithStackedValues:@[ @5, @11, @14 ]],
[[ORKValueStack alloc] initWithStackedValues:@[ @7, @13, @20 ]],
[[ORKValueStack alloc] initWithStackedValues:@[ @10, @13, @25 ]],
]
];
}
return self;
}
- (ORKValueStack *)graphChartView:(ORKGraphChartView *)graphChartView dataPointForPointIndex:(NSInteger)pointIndex plotIndex:(NSInteger)plotIndex {
return self.plotPoints[plotIndex][pointIndex];
}
@end
#endif
static const CGFloat BarWidth = 10.0;
@interface ORKBarGraphChartView ()
@property (nonatomic) NSMutableArray<NSMutableArray<ORKValueStack *> *> *dataPoints; // Actual data
@property (nonatomic) NSMutableArray<NSMutableArray<ORKValueStack *> *> *yAxisPoints; // Normalized for the plot view height
@end
@implementation ORKBarGraphChartView
@dynamic dataSource;
@dynamic dataPoints;
@dynamic yAxisPoints;
#pragma mark - Draw
- (ORKValueStack *)dataPointForPointIndex:(NSInteger)pointIndex plotIndex:(NSInteger)plotIndex {
return [self.dataSource graphChartView:self dataPointForPointIndex:pointIndex plotIndex:plotIndex];
}
- (ORKValueStack *)dummyPoint {
return [ORKValueStack new];
}
- (BOOL)shouldDrawLinesForPlotIndex:(NSInteger)plotIndex {
return YES;
}
- (NSMutableArray<ORKValueStack *> *)normalizedCanvasDataPointsForPlotIndex:(NSInteger)plotIndex canvasHeight:(CGFloat)viewHeight {
NSMutableArray<ORKValueStack *> *normalizedDataPoints = [NSMutableArray new];
if (plotIndex < self.dataPoints.count) {
NSUInteger pointCount = self.dataPoints[plotIndex].count;
for (NSUInteger pointIndex = 0; pointIndex < pointCount; pointIndex++) {
NSMutableArray *normalizedDoubleStackValues = [NSMutableArray new];
ORKValueStack *dataPointValue = self.dataPoints[plotIndex][pointIndex];
if (!dataPointValue.isUnset) {
double range = self.maximumValue - self.minimumValue;
double sum = 0;
for (NSNumber *value in dataPointValue.stackedValues) {
// normalizedDoubleStackValues holds absolute canvas y-positions corresponding to each point
// (rather than incremental y-positions as the dataPoints valueStacks hold).
// E.g. (canvas height = 100)
// dataPoint valueStack = {10, 10, 20}
// ->
// normalized valueStack = {25, 50, 100}
sum += value.doubleValue;
double normalizedValue = (sum - self.minimumValue) / range * viewHeight;
normalizedValue = floor(viewHeight - normalizedValue);
[normalizedDoubleStackValues addObject:@(normalizedValue)];
}
}
[normalizedDataPoints addObject:[[ORKValueStack alloc] initWithStackedValues:normalizedDoubleStackValues]];
}
}
return normalizedDataPoints;
}
- (void)calculateMinAndMaxValues {
self.minimumValue = ORKDoubleInvalidValue;
self.maximumValue = ORKDoubleInvalidValue;
BOOL maximumValueProvided = NO;
if ([self.dataSource respondsToSelector:@selector(minimumValueForGraphChartView:)]) {
self.minimumValue = [self.dataSource minimumValueForGraphChartView:self];
} else {
self.minimumValue = 0;
}
if ([self.dataSource respondsToSelector:@selector(maximumValueForGraphChartView:)]) {
self.maximumValue = [self.dataSource maximumValueForGraphChartView:self];
maximumValueProvided = YES;
}
if (!maximumValueProvided) {
NSInteger numberOfPlots = [self numberOfPlots];
for (NSInteger plotIndex = 0; plotIndex < numberOfPlots; plotIndex++) {
NSInteger numberOfPlotPoints = self.dataPoints[plotIndex].count;
for (NSInteger pointIndex = 0; pointIndex < numberOfPlotPoints; pointIndex++) {
ORKValueStack *point = self.dataPoints[plotIndex][pointIndex];
if (!maximumValueProvided &&
point.totalValue != ORKDoubleInvalidValue &&
((self.maximumValue == ORKDoubleInvalidValue) || (point.totalValue > self.maximumValue))) {
self.maximumValue = point.totalValue;
}
}
}
}
if (self.maximumValue == ORKDoubleInvalidValue) {
self.maximumValue = 0;
}
}
- (UIColor *)colorForPlotIndex:(NSInteger)plotIndex subpointIndex:(NSInteger)subpointIndex totalSubpoints:(NSInteger)totalSubpoints {
return ORKOpaqueColorWithReducedAlphaFromBaseColor([super colorForPlotIndex:plotIndex subpointIndex:subpointIndex totalSubpoints:(NSInteger)totalSubpoints], subpointIndex, totalSubpoints);
}
- (void)updateLineLayersForPlotIndex:(NSInteger)plotIndex {
NSUInteger pointCount = self.dataPoints[plotIndex].count;
for (NSUInteger pointIndex = 0; pointIndex < pointCount; pointIndex++) {
ORKValueStack *dataPointValue = self.dataPoints[plotIndex][pointIndex];
NSMutableArray *lineLayers = [NSMutableArray new];
if (!dataPointValue.isUnset) {
NSUInteger numberOfStackedValues = dataPointValue.stackedValues.count;
for (NSUInteger index = 0; index < numberOfStackedValues; index++) {
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.strokeColor = [self colorForPlotIndex:plotIndex subpointIndex:index totalSubpoints:numberOfStackedValues].CGColor;
lineLayer.lineWidth = BarWidth;
[self.plotView.layer addSublayer:lineLayer];
[lineLayers addObject:lineLayer];
}
}
[self.lineLayers[plotIndex] addObject:lineLayers];
}
}
- (void)layoutLineLayersForPlotIndex:(NSInteger)plotIndex {
NSUInteger lineLayerIndex = 0;
double positionOnXAxis = ORKDoubleInvalidValue;
ORKValueStack *positionsOnYAxis = nil;
NSUInteger pointCount = self.yAxisPoints[plotIndex].count;
for (NSUInteger pointIndex = 0; pointIndex < pointCount; pointIndex++) {
float previousYValue = self.plotView.bounds.size.height;
ORKValueStack *dataPointValue = self.dataPoints[plotIndex][pointIndex];
positionsOnYAxis = self.yAxisPoints[plotIndex][pointIndex];
if (!dataPointValue.isUnset) {
NSUInteger numberOfSubpoints = positionsOnYAxis.stackedValues.count;
for (NSUInteger subpointIndex = 0; subpointIndex < numberOfSubpoints; subpointIndex++) {
double positionOnYAxis = positionsOnYAxis.stackedValues[subpointIndex].doubleValue;
UIBezierPath *linePath = [UIBezierPath bezierPath];
double barHeight = fabs(positionOnYAxis - previousYValue);
positionOnXAxis = xAxisPoint(pointIndex, self.numberOfXAxisPoints, self.plotView.bounds.size.width);
positionOnXAxis += [self xOffsetForPlotIndex:plotIndex];
[linePath moveToPoint:CGPointMake(positionOnXAxis, previousYValue + scalePixelAdjustment())];
[linePath addLineToPoint:CGPointMake(positionOnXAxis, previousYValue + scalePixelAdjustment() - barHeight)];
previousYValue = positionOnYAxis;
CAShapeLayer *lineLayer = self.lineLayers[plotIndex][pointIndex][subpointIndex];
lineLayer.path = linePath.CGPath;
lineLayerIndex++;
}
}
}
}
#pragma mark - Scrubbing
- (double)scrubbingValueForPlotIndex:(NSInteger)plotIndex pointIndex:(NSInteger)pointIndex {
return self.dataPoints[plotIndex][pointIndex].totalValue;
}
- (double)scrubbingYAxisPointForPlotIndex:(NSInteger)plotIndex pointIndex:(NSInteger)pointIndex {
return self.yAxisPoints[plotIndex][pointIndex].stackedValues.lastObject.doubleValue; // totalValue is not normalized to canvas coordinates
}
- (CGFloat)xOffsetForPlotIndex:(NSInteger)plotIndex {
return xOffsetForPlotIndex(plotIndex, [self numberOfPlots], BarWidth);
}
- (CGFloat)snappedXPosition:(CGFloat)xPosition plotIndex:(NSInteger)plotIndex {
return [super snappedXPosition:xPosition plotIndex:plotIndex] + [self xOffsetForPlotIndex:plotIndex];
}
- (NSInteger)pointIndexForXPosition:(CGFloat)xPosition plotIndex:(NSInteger)plotIndex {
return [super pointIndexForXPosition:xPosition - [self xOffsetForPlotIndex:plotIndex] plotIndex:plotIndex];
}
- (BOOL)isXPositionSnapped:(CGFloat)xPosition plotIndex:(NSInteger)plotIndex {
return [super isXPositionSnapped:xPosition - [self xOffsetForPlotIndex:plotIndex] plotIndex:plotIndex];
}
#pragma mark - Interface Builder designable
- (void)prepareForInterfaceBuilder {
[super prepareForInterfaceBuilder];
#if TARGET_INTERFACE_BUILDER
self.dataSource = [ORKIBBarGraphChartViewDataSource sharedInstance];
#endif
}
@end
-152
View File
@@ -1,152 +0,0 @@
/*
Copyright (c) 2015, Apple Inc. All rights reserved.
Copyright (c) 2015, James Cox.
Copyright (c) 2015, Ramsundar Shandilya.
Copyright (c) 2015-2016, Ricardo Sánchez-Sáez.
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.
*/
@import UIKit;
#import <ResearchKit/ORKDefines.h>
NS_ASSUME_NONNULL_BEGIN
/**
Defines the common attributes of chart values.
*/
@protocol ORKValueCollectionType
/**
A Boolean value indicating that the value is unset. (read-only)
*/
@property (nonatomic, readonly) BOOL isUnset;
@end
/**
The `ORKValueRange` class represents a range between two values of the double type. It can be used
in graph chart plots to draw value ranges.
*/
ORK_CLASS_AVAILABLE
@interface ORKValueRange : NSObject <ORKValueCollectionType>
/**
Returns a value range initialized using the specified `minimumValue` and `maximumValue`.
@param minimumValue The `minimumValue` to set.
@param maximumValue The `maximumValue` to set.
@return A value range object.
*/
- (instancetype)initWithMinimumValue:(double)minimumValue maximumValue:(double)maximumValue NS_DESIGNATED_INITIALIZER;
/**
Returns a value range initialized using the specified `value` for both `minimumValue` and
`maximumValue`. This is useful for plotting data points that model a single value with no range.
This method is a convenience initializer.
@param value The `minimumValue` and `maximumValue` to set.
@return A value range object.
*/
- (instancetype)initWithValue:(double)value;
/**
Returns a value range initialized using the `ORKDoubleInvalidValue` value for both `minimumValue`
and `maximumValue`. This denotes an unset or invalid value range. It is useful, for example, for
representing unavailable data in discontinous graph chart plots.
This method is a convenience initializer.
@return A value range object initialized with the `ORKDoubleInvalidValue` value.
*/
- (instancetype)init;
/**
The upper limit of the value range.
*/
@property (nonatomic) double maximumValue;
/**
The lower limit of the value range.
*/
@property (nonatomic) double minimumValue;
/**
A Boolean value indicating that `minimumValue` is equal to `maximumValue`. (read-only)
*/
@property (nonatomic, readonly) BOOL isEmptyRange;
/**
A Boolean value indicating that both `minimum value` and `maximum value` are equal to the
`ORKDoubleInvalidValue` value. (read-only)
*/
@property (nonatomic, readonly) BOOL isUnset;
@end
/**
The `ORKValueStack` class represents an arbitrary list of values of the double type. It can be used
in graph chart plots to draw stacked values.
*/
ORK_CLASS_AVAILABLE
@interface ORKValueStack : NSObject <ORKValueCollectionType>
/**
Returns an empty value stack.
@return An empty value stack object.
*/
- (instancetype)init;
/**
Returns a value stack initialized with the passed `NSNumber` array.
@param stackedValues An array containing `NSNumber` objects.
@return A value stack object initialized with the passed `NSNumber` values.
*/
- (instancetype)initWithStackedValues:(NSArray<NSNumber *> *)stackedValues NS_DESIGNATED_INITIALIZER;
/**
An array of `NSNumber` objects, each one representing a stacked double value.
*/
@property (nonatomic, copy, readonly) NSArray<NSNumber *> *stackedValues;
/**
The total sum of the stacked double values
*/
@property (nonatomic) double totalValue;
@end
NS_ASSUME_NONNULL_END
-163
View File
@@ -1,163 +0,0 @@
/*
Copyright (c) 2015, Apple Inc. All rights reserved.
Copyright (c) 2015, James Cox.
Copyright (c) 2015, Ramsundar Shandilya.
Copyright (c) 2015-2016, Ricardo Sánchez-Sáez.
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.
*/
#import "ORKChartTypes.h"
#import "ORKHelpers_Internal.h"
@implementation ORKValueRange
- (instancetype)initWithMinimumValue:(double)minimumValue maximumValue:(double)maximumValue {
if (maximumValue < minimumValue) {
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:@"maximumValue cannot be lower than minimumValue"
userInfo:nil];
}
self = [super init];
if (self) {
_minimumValue = minimumValue;
_maximumValue = maximumValue;
}
return self;
}
- (instancetype)init {
return [self initWithMinimumValue:ORKDoubleInvalidValue maximumValue:ORKDoubleInvalidValue];
}
- (instancetype)initWithValue:(double)value {
return [self initWithMinimumValue:value maximumValue:value];
}
- (BOOL)isUnset {
return (_minimumValue == ORKDoubleInvalidValue && _maximumValue == ORKDoubleInvalidValue);
}
- (BOOL)isEmptyRange {
return (_minimumValue == _maximumValue);
}
- (NSString *)description {
NSString *minimumValueString = (_minimumValue == ORKDoubleInvalidValue) ? @"ORKDoubleInvalidValue" : [NSString stringWithFormat:@"%0.0f", _minimumValue] ;
NSString *maximumValueString = (_maximumValue == ORKDoubleInvalidValue) ? @"ORKDoubleInvalidValue" : [NSString stringWithFormat:@"%0.0f", _maximumValue] ;
return [NSString stringWithFormat:@"<%@: %p; min = %@; max = %@>", self.class.description, self, minimumValueString, maximumValueString];
}
#pragma mark - Accessibility
- (NSString *)accessibilityLabel {
if (self.isUnset) {
return nil;
}
if (self.isEmptyRange || _minimumValue == _maximumValue) {
return @(_maximumValue).stringValue;
} else {
NSString *rangeFormat = ORKLocalizedString(@"AX_GRAPH_RANGE_FORMAT_%@_%@", nil);
return [NSString stringWithFormat:rangeFormat, @(_minimumValue).stringValue, @(_maximumValue).stringValue];
}
}
@end
@implementation ORKValueStack
- (instancetype)init {
return [self initWithStackedValues:@[]];
}
- (instancetype)initWithStackedValues:(NSArray<NSNumber *> *)stackedValues {
self = [super init];
if (self) {
if (stackedValues.count == 0) {
_totalValue = ORKDoubleInvalidValue;
} else {
_totalValue = 0;
for (NSNumber *number in stackedValues) {
if (![number isKindOfClass:[NSNumber class]]) {
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:@"stackedValues must only contain NSNumber objects"
userInfo:nil];
}
_totalValue += number.doubleValue;
}
}
_stackedValues = [stackedValues copy];
}
return self;
}
- (BOOL)isUnset {
return !_stackedValues || _stackedValues.count == 0;
}
- (NSString *)description {
NSMutableString *mutableString = [NSMutableString new];
[mutableString appendFormat:@"<%@: %p; (", self.class.description, self];
NSUInteger numberOfStackedValues = _stackedValues.count;
for (NSInteger index = 0; index < numberOfStackedValues; index++) {
[mutableString appendFormat:@"%0.0f", _stackedValues[index].doubleValue];
if (index < numberOfStackedValues - 1) {
[mutableString appendString:@", "];
}
}
[mutableString appendString:@")>"];
return [mutableString copy];
}
#pragma mark - Accessibility
- (NSString *)accessibilityLabel {
if (self.isUnset) {
return nil;
}
NSMutableString *mutableString = [[NSMutableString alloc] initWithFormat:@"%@ %@",
ORKLocalizedString(@"AX_GRAPH_STACK_PREFIX", nil), _stackedValues[0].stringValue];
NSUInteger numberOfStackedValues = _stackedValues.count;
for (NSInteger index = 1; index < numberOfStackedValues; index++) {
[mutableString appendString:@", "];
if (index == (numberOfStackedValues - 1)) {
[mutableString appendString:ORKLocalizedString(@"AX_GRAPH_AND_SEPARATOR", nil)];
}
[mutableString appendFormat:@"%@", _stackedValues[index].stringValue];
}
return [mutableString copy];
}
@end
@@ -1,62 +0,0 @@
/*
Copyright (c) 2015, Apple Inc. All rights reserved.
Copyright (c) 2015, James Cox.
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.
*/
@import UIKit;
#import <ResearchKit/ORKGraphChartView.h>
NS_ASSUME_NONNULL_BEGIN
/**
The `ORKDiscreteGraphChartView` class presents data provided by an object conforming to the
`ORKValueRangeGraphChartViewDataSource` protocol as a discrete graph of ranged points.
You can optionally display a line connecting each ranged point pair.
By default, the primary plot is colored by the `tintColor`, and any additional plots are colored
using the `referenceLineColor` property. You can customize the plot colors by implementing the
`-graphChartView:colorForPlotIndex:` method in the data source.
*/
ORK_CLASS_AVAILABLE
@interface ORKDiscreteGraphChartView : ORKValueRangeGraphChartView
/**
A Boolean value indicating whether to draw a line connecting the minimum value and maximum value of
each ranged point represented by the graph view.
The default value for this property is `YES`.
*/
@property (nonatomic) IBInspectable BOOL drawsConnectedRanges;
@end
NS_ASSUME_NONNULL_END

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