├── .DS_Store
├── Design
├── icon_1.png
├── screens.png
├── screen_1.png
├── screen_2.png
└── screen_3.png
├── Resources
├── tick.png
├── ellipses.png
├── screen_1.png
├── screen_2.png
├── screen_3.png
├── stop_button.png
├── finish_button.png
├── record_button.png
└── curved_white_button.png
├── BeatsTests
├── .DS_Store
├── TestHelpers.swift
├── RecordTests.swift
├── Info.plist
├── SessionTests.swift
├── MockCentralManagerTests.swift
├── HeartRateViewControllerTests.swift
├── SessionRecorderTests.swift
├── HeartRateRecorderIntegrationTests.swift
├── MockBluetoothControllerPeripheralTests.swift
├── MockPeripheralTests.swift
├── MockBluetoothControllerConnectionTests.swift
├── HeartRateKitTests.swift
├── ScanningViewControllerTests.swift
└── RecordingControlsViewController.swift
├── .gitignore
├── Beats
├── Assets.xcassets
│ ├── Contents.json
│ ├── tick.imageset
│ │ ├── tick.png
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── Icon-40.png
│ │ ├── Icon-76.png
│ │ ├── Icon-40@2x.png
│ │ ├── Icon-40@3x.png
│ │ ├── Icon-60@2x.png
│ │ ├── Icon-60@3x.png
│ │ ├── Icon-76@2x.png
│ │ ├── Icon-Small.png
│ │ ├── Icon-83.5@2x.png
│ │ ├── Icon-Small@2x.png
│ │ ├── Icon-Small@3x.png
│ │ └── Contents.json
│ ├── ellipses.imageset
│ │ ├── ellipses.png
│ │ └── Contents.json
│ ├── stop_button.imageset
│ │ ├── stop_button.png
│ │ └── Contents.json
│ ├── finish_button.imageset
│ │ ├── finish_button.png
│ │ └── Contents.json
│ ├── record_button.imageset
│ │ ├── record_button.png
│ │ └── Contents.json
│ └── curved_white_button.imageset
│ │ ├── curved_white_button.png
│ │ └── Contents.json
├── MockHeartRateMode.swift
├── SessionRecorderDelegate.swift
├── BluetoothControllerState.swift
├── BluetoothControllerProtocol.swift
├── HeartRateKitUIDelegate.swift
├── UIButtonWithAspectFit.swift
├── ArrayExtension.swift
├── Record.swift
├── BluetoothControllerDelegateProtocol.swift
├── MockCentralManagerDelegate.swift
├── MockPeripheralDelegate.swift
├── ViewController.swift
├── MockCharacteristic.swift
├── Session.swift
├── HeartRateViewController.swift
├── SessionRecorder.swift
├── Info.plist
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── MockCentralManager.swift
├── AppDelegate.swift
├── HeartRateKit.swift
├── RecordingControlsViewController.swift
├── ScanningViewController.swift
├── MockBluetoothController.swift
├── MockPeripheral.swift
└── BluetoothController.swift
├── Beats.xcodeproj
├── xcuserdata
│ └── yvette.xcuserdatad
│ │ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ ├── xcschememanagement.plist
│ │ └── Beats.xcscheme
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── yvette.xcuserdatad
│ │ └── xcdebugger
│ │ └── Expressions.xcexplist
└── project.pbxproj
├── BeatsUITests
├── Info.plist
└── UITest.swift
└── README.md
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/.DS_Store
--------------------------------------------------------------------------------
/Design/icon_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Design/icon_1.png
--------------------------------------------------------------------------------
/Design/screens.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Design/screens.png
--------------------------------------------------------------------------------
/Resources/tick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Resources/tick.png
--------------------------------------------------------------------------------
/BeatsTests/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/BeatsTests/.DS_Store
--------------------------------------------------------------------------------
/Design/screen_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Design/screen_1.png
--------------------------------------------------------------------------------
/Design/screen_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Design/screen_2.png
--------------------------------------------------------------------------------
/Design/screen_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Design/screen_3.png
--------------------------------------------------------------------------------
/Resources/ellipses.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Resources/ellipses.png
--------------------------------------------------------------------------------
/Resources/screen_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Resources/screen_1.png
--------------------------------------------------------------------------------
/Resources/screen_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Resources/screen_2.png
--------------------------------------------------------------------------------
/Resources/screen_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Resources/screen_3.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.xcuserstate
2 | project.xcworkspace/
3 | xcuserdata/
4 |
5 | build/
6 | DerivedData/
7 |
--------------------------------------------------------------------------------
/Resources/stop_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Resources/stop_button.png
--------------------------------------------------------------------------------
/Resources/finish_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Resources/finish_button.png
--------------------------------------------------------------------------------
/Resources/record_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Resources/record_button.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Resources/curved_white_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Resources/curved_white_button.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/tick.imageset/tick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/tick.imageset/tick.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/AppIcon.appiconset/Icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/AppIcon.appiconset/Icon-40.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/AppIcon.appiconset/Icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/AppIcon.appiconset/Icon-76.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/ellipses.imageset/ellipses.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/ellipses.imageset/ellipses.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/AppIcon.appiconset/Icon-Small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/AppIcon.appiconset/Icon-Small.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/stop_button.imageset/stop_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/stop_button.imageset/stop_button.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/finish_button.imageset/finish_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/finish_button.imageset/finish_button.png
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/record_button.imageset/record_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/record_button.imageset/record_button.png
--------------------------------------------------------------------------------
/Beats.xcodeproj/xcuserdata/yvette.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/curved_white_button.imageset/curved_white_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yvettecook/Beats/HEAD/Beats/Assets.xcassets/curved_white_button.imageset/curved_white_button.png
--------------------------------------------------------------------------------
/Beats.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Beats/MockHeartRateMode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockHeartRateMode.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 19/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum MockHeartRateMode {
12 | case SteadyResting
13 | }
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/finish_button.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "finish_button.png",
6 | "scale" : "1x"
7 | }
8 | ],
9 | "info" : {
10 | "version" : 1,
11 | "author" : "xcode"
12 | }
13 | }
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/record_button.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "record_button.png",
6 | "scale" : "1x"
7 | }
8 | ],
9 | "info" : {
10 | "version" : 1,
11 | "author" : "xcode"
12 | }
13 | }
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/stop_button.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "stop_button.png",
6 | "scale" : "1x"
7 | }
8 | ],
9 | "info" : {
10 | "version" : 1,
11 | "author" : "xcode"
12 | }
13 | }
--------------------------------------------------------------------------------
/Beats/SessionRecorderDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionRecorderDelegate.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 29/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | protocol SessionRecorderDelegate {
10 |
11 | func recorderDidUpdateState(state: SessionRecorderState)
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/Beats/BluetoothControllerState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BluetoothControllerState.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 21/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum BluetoothControllerState {
12 | case StartedUp
13 | case Scanning
14 | case FoundMonitor
15 | case ConnectedMonitor
16 | }
--------------------------------------------------------------------------------
/Beats/BluetoothControllerProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BluetoothControllerProtocol.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 08/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | protocol BluetoothControllerProtocol {
10 |
11 | var delegate: BluetoothControllerDelegate? { get set }
12 |
13 | func scanForAvailableMonitors()
14 |
15 | }
--------------------------------------------------------------------------------
/Beats/HeartRateKitUIDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HeartRateKitUIDelegate.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 20/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol HeartRateKitUIDelegate {
12 |
13 | func hrKitDidUpdateState(state: HeartRateKitState)
14 | func hrKitDidUpdateBPM(bpm: Int)
15 |
16 | }
--------------------------------------------------------------------------------
/Beats/UIButtonWithAspectFit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIButtonWithAspectFit.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 29/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UIButtonWithAspectFit: UIButton {
12 |
13 | override func awakeFromNib() {
14 | self.imageView?.contentMode = .ScaleAspectFit
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/Beats/ArrayExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayExtension.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 19/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Array {
12 |
13 | func randomItem() -> Element {
14 | let index = Int(arc4random_uniform(UInt32(self.count)))
15 | return self[index]
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/Beats/Record.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Record.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 02/02/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class Record: NSObject {
12 |
13 | var value: Int!
14 | var time: NSDate!
15 |
16 | init(value: Int) {
17 | self.value = value
18 | self.time = NSDate()
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/tick.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "tick.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/ellipses.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "ellipses.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Beats/BluetoothControllerDelegateProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BluetoothControllerDelegateProtocol.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 10/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol BluetoothControllerDelegate {
12 |
13 | func bluetooothControllerStateChanged(state: BluetoothControllerState)
14 |
15 | func heartRateUpdated(hr: Int)
16 |
17 | }
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/curved_white_button.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "curved_white_button.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/BeatsTests/TestHelpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestHelpers.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 17/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | extension XCTestCase {
12 |
13 | func asyncTest(completion: () -> Void, wait: Int64){
14 | let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), wait * Int64(NSEC_PER_SEC))
15 |
16 | dispatch_after(time, dispatch_get_main_queue()) {
17 | completion()
18 | }
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/Beats/MockCentralManagerDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockCentralManagerDelegate.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 10/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol MockCentralManagerDelegate {
12 |
13 | func centralManager(central: MockCentralManager, didDiscoverPeripheral peripheral: MockPeripheral,
14 | advertisementData: [String : AnyObject], RSSI: NSNumber)
15 |
16 | func centralManager(central: MockCentralManager, didConnectPeripheral peripheral: MockPeripheral)
17 |
18 | }
--------------------------------------------------------------------------------
/Beats/MockPeripheralDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockPeripheralDelegate.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 17/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import CoreBluetooth
10 |
11 | protocol MockPeripheralDelegate {
12 |
13 | func peripheral(peripheral: MockPeripheral, didDiscoverServices error: NSError?)
14 | func peripheral(peripheral: MockPeripheral, didDiscoverCharacteristics error: NSError?)
15 | func peripheral(peripheral: MockPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?)
16 |
17 | }
--------------------------------------------------------------------------------
/BeatsTests/RecordTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RecordTests.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 03/02/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Beats
11 |
12 | class RecordTests: XCTestCase {
13 |
14 | var record: Record!
15 |
16 | override func setUp() {
17 | record = Record(value: 1)
18 | super.setUp()
19 | }
20 |
21 | override func tearDown() {
22 | super.tearDown()
23 | }
24 |
25 | func testHasTime() {
26 | XCTAssertNotNil(record.time)
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/Beats/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 08/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 | // Do any additional setup after loading the view, typically from a nib.
16 | }
17 |
18 | override func didReceiveMemoryWarning() {
19 | super.didReceiveMemoryWarning()
20 | // Dispose of any resources that can be recreated.
21 | }
22 |
23 |
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/Beats/MockCharacteristic.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockCharacteristic.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 19/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import CoreBluetooth
10 |
11 | final class MockCharacteristic: CBMutableCharacteristic {
12 |
13 | var trueCharacteristic: CBMutableCharacteristic?
14 | var mockValue: NSData?
15 |
16 | init(type: CBUUID,
17 | properties: CBCharacteristicProperties,
18 | permissions: CBAttributePermissions) {
19 | super.init(type: type, properties: properties, value: nil, permissions: permissions)
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/Beats/Session.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Session.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 02/02/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class Session: NSObject {
12 |
13 | var values: [Record]?
14 |
15 | var startTime: NSDate?
16 | var endTime: NSDate?
17 | var name: String?
18 |
19 | override init() {
20 | values = [Record]()
21 | startTime = NSDate()
22 | }
23 |
24 | func addValue(value: Int) {
25 | let record = Record(value: value)
26 | values?.append(record)
27 | }
28 |
29 | func end() {
30 | endTime = NSDate()
31 | }
32 |
33 | func name(name: String) {
34 | self.name = name
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/BeatsTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/BeatsUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Beats.xcodeproj/xcuserdata/yvette.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Beats.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 1AA2555F1C40246300A7DAE5
16 |
17 | primary
18 |
19 |
20 | 1AA255731C40246300A7DAE5
21 |
22 | primary
23 |
24 |
25 | 1AA2557E1C40246300A7DAE5
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/BeatsTests/SessionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionTests.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 02/02/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Beats
11 |
12 | class SessionTests: XCTestCase {
13 |
14 | var session: Session!
15 |
16 | override func setUp() {
17 | session = Session()
18 | super.setUp()
19 | }
20 |
21 | override func tearDown() {
22 | super.tearDown()
23 | }
24 |
25 | func testHasAStartTime() {
26 | XCTAssertNotNil(session.startTime)
27 | }
28 |
29 | func testHasEndTime() {
30 | session.end()
31 | XCTAssertNotNil(session.endTime)
32 | }
33 |
34 | func testCanGiveName() {
35 | session.name("Test")
36 | XCTAssertEqual(session.name, "Test")
37 | }
38 |
39 | func testCanAddValue() {
40 | session.addValue(1)
41 | XCTAssertEqual(session.values![0].value, 1)
42 | }
43 |
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/BeatsUITests/UITest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITest.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 15/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class UITest: XCTestCase {
12 |
13 | override func setUp() {
14 | super.setUp()
15 |
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 |
18 | // In UI tests it is usually best to stop immediately when a failure occurs.
19 | continueAfterFailure = false
20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
21 | XCUIApplication().launch()
22 |
23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
24 | }
25 |
26 | override func tearDown() {
27 | // Put teardown code here. This method is called after the invocation of each test method in the class.
28 | super.tearDown()
29 | }
30 |
31 | // func testExample() {
32 | // Use recording to get started writing UI tests.
33 | // Use XCTAssert and related functions to verify your tests produce the correct results.
34 | // }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/Beats/HeartRateViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HeartRateViewController.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 20/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class HeartRateViewController: UIViewController, HeartRateKitUIDelegate {
12 |
13 | var heartRateKit: HeartRateKit?
14 | var recordingControlsVC: RecordingControlsViewController?
15 | var sessionRecorder = SessionRecorder.sharedInstance
16 |
17 | @IBOutlet weak var bpmLabel: UILabel!
18 |
19 | override func viewDidLoad() {
20 | heartRateKit = HeartRateKit.sharedInstance
21 | heartRateKit?.uiDelegate = self
22 | sessionRecorder.newSession()
23 | }
24 |
25 | func hrKitDidUpdateState(state: HeartRateKitState) {
26 |
27 | }
28 |
29 | func hrKitDidUpdateBPM(bpm: Int) {
30 | bpmLabel.text = "\(bpm)"
31 | sessionRecorder.addValue(bpm)
32 | }
33 |
34 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
35 | if segue.identifier == "EmbedControls" {
36 | guard
37 | let vc = segue.destinationViewController as? RecordingControlsViewController
38 | else { fatalError("Incorrect EmbedControls segue") }
39 | recordingControlsVC = vc
40 | }
41 | }
42 |
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/Beats/SessionRecorder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionRecorder.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 28/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class SessionRecorder: NSObject {
12 |
13 | var delegate: SessionRecorderDelegate?
14 |
15 | var currentSession: Session?
16 |
17 | var state: SessionRecorderState {
18 | didSet {
19 | delegate?.recorderDidUpdateState(state)
20 | }
21 | }
22 |
23 | static let sharedInstance = SessionRecorder()
24 |
25 | private override init() {
26 | state = .Inactive
27 | super.init()
28 | }
29 |
30 | // MARK: Recording
31 |
32 | func startRecording() {
33 | state = .Recording
34 | }
35 |
36 | func pauseRecording() {
37 | state = .Paused
38 | }
39 |
40 | func finishRecording() {
41 | state = .Finished
42 | currentSession?.end()
43 | }
44 |
45 | // MARK: Sessions
46 |
47 | func newSession() {
48 | currentSession = Session()
49 | }
50 |
51 | func addValue(value: Int) {
52 | if state == .Recording {
53 | currentSession?.addValue(value)
54 | }
55 | }
56 |
57 | }
58 |
59 | enum SessionRecorderState {
60 | case Inactive
61 | case Recording
62 | case Paused
63 | case Finished
64 | }
--------------------------------------------------------------------------------
/Beats/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Beats/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Beats/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "29x29",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-Small@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "29x29",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-Small@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "40x40",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-40@2x.png",
19 | "scale" : "2x"
20 | },
21 | {
22 | "size" : "40x40",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-40@3x.png",
25 | "scale" : "3x"
26 | },
27 | {
28 | "size" : "60x60",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-60@2x.png",
31 | "scale" : "2x"
32 | },
33 | {
34 | "size" : "60x60",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-60@3x.png",
37 | "scale" : "3x"
38 | },
39 | {
40 | "size" : "29x29",
41 | "idiom" : "ipad",
42 | "filename" : "Icon-Small.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "29x29",
47 | "idiom" : "ipad",
48 | "filename" : "Icon-Small@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "40x40",
53 | "idiom" : "ipad",
54 | "filename" : "Icon-40.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "40x40",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-40@2x.png",
61 | "scale" : "2x"
62 | },
63 | {
64 | "size" : "76x76",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-76.png",
67 | "scale" : "1x"
68 | },
69 | {
70 | "size" : "76x76",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-76@2x.png",
73 | "scale" : "2x"
74 | },
75 | {
76 | "size" : "83.5x83.5",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-83.5@2x.png",
79 | "scale" : "2x"
80 | }
81 | ],
82 | "info" : {
83 | "version" : 1,
84 | "author" : "xcode"
85 | }
86 | }
--------------------------------------------------------------------------------
/BeatsTests/MockCentralManagerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockCentralManagerTests.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 10/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import CoreBluetooth
11 |
12 | @testable import Beats
13 |
14 | class MockCentralManagerTests: XCTestCase {
15 |
16 | var mockCentralManager: MockCentralManager!
17 | let mockPeripheral = MockPeripheral()
18 |
19 | let heartRateUUID = CBUUID(string: "180D")
20 | var heartRateService : CBMutableService?
21 |
22 | override func setUp() {
23 | mockCentralManager = MockCentralManager(delegate: MockBluetoothController(), queue: nil)
24 | heartRateService = CBMutableService(type: heartRateUUID, primary: true)
25 | super.setUp()
26 | }
27 |
28 | override func tearDown() {
29 | super.tearDown()
30 | }
31 |
32 | func testHasDelegate() {
33 | XCTAssertNotNil(mockCentralManager.delegate)
34 | }
35 |
36 | func testRespondsToScanForPeripheralsWithServices() {
37 | mockCentralManager.scanForPeripheralsWithServices(nil, options: nil)
38 | XCTAssertTrue(mockCentralManager.scanForPeripheralsWithServicesCalled)
39 | }
40 |
41 | func testFindsPeripheralOnScanForPeripherals() {
42 | weak var expectation = expectationWithDescription("Should find Peripheral when scanning")
43 |
44 | mockCentralManager.scanForPeripheralsWithServices(nil, options: nil)
45 |
46 | let completion = { () -> Void in
47 | XCTAssertTrue(self.mockCentralManager.discoveredPeripheral)
48 | expectation?.fulfill()
49 | }
50 |
51 | asyncTest(completion, wait: 2)
52 |
53 | waitForExpectationsWithTimeout(3, handler: nil)
54 | }
55 |
56 | func testCanConnectToPeripheral() {
57 | mockCentralManager.connectPeripheral(mockPeripheral, options: nil)
58 |
59 | XCTAssertTrue(self.mockCentralManager.connectedToPeripheral)
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/BeatsTests/HeartRateViewControllerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HeartRateViewControllerTests.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 21/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | @testable import Beats
12 |
13 | class HeartRateViewControllerTests: XCTestCase {
14 |
15 | var heartRateVC: HeartRateViewController!
16 |
17 | override func setUp() {
18 | super.setUp()
19 |
20 | let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
21 | heartRateVC = storyboard.instantiateViewControllerWithIdentifier("HeartRateViewController") as! HeartRateViewController
22 | UIApplication.sharedApplication().keyWindow!.rootViewController = heartRateVC
23 |
24 | XCTAssertNotNil(heartRateVC.view)
25 |
26 | heartRateVC.heartRateKit?.mode = .Demo
27 | heartRateVC.heartRateKit?.scanForMonitors()
28 | }
29 |
30 | override func tearDown() {
31 | super.tearDown()
32 | }
33 |
34 | func testHasHeartRateKit() {
35 | XCTAssertNotNil(heartRateVC.heartRateKit)
36 | let delegate = heartRateVC.heartRateKit!.uiDelegate! as? HeartRateViewController
37 | XCTAssertNotNil(delegate)
38 | XCTAssertEqual(heartRateVC, delegate)
39 | }
40 |
41 | func testHeartRateViewDisplayCurrentHR() {
42 | weak var expectation = expectationWithDescription("Current heart rate should be displayed")
43 |
44 | let completion = { () -> Void in
45 | let currentHR = self.heartRateVC.heartRateKit?.currentHeartRate
46 | let displayBpm = self.heartRateVC.bpmLabel.text
47 | XCTAssertEqual(displayBpm, "\(currentHR!)")
48 | expectation?.fulfill()
49 | }
50 |
51 | asyncTest(completion, wait: 7)
52 |
53 | waitForExpectationsWithTimeout(7.5, handler: nil)
54 | }
55 |
56 | func testHasARecordingControlsVC() {
57 | XCTAssertNotNil(heartRateVC.recordingControlsVC)
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/BeatsTests/SessionRecorderTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionRecorderTests.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 28/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Beats
11 |
12 | class SessionRecorderTests: XCTestCase {
13 |
14 | var sessionRecorder: SessionRecorder!
15 |
16 | override func setUp() {
17 | sessionRecorder = SessionRecorder.sharedInstance
18 | super.setUp()
19 | }
20 |
21 | override func tearDown() {
22 | super.tearDown()
23 | }
24 |
25 | func testSingletonIsSame() {
26 | let recorder1 = SessionRecorder.sharedInstance
27 | XCTAssertEqual(sessionRecorder, recorder1)
28 | }
29 |
30 | func testCanStartRecording() {
31 | sessionRecorder.startRecording()
32 | XCTAssertEqual(sessionRecorder.state, SessionRecorderState.Recording)
33 | }
34 |
35 | func testCanStopRecording() {
36 | sessionRecorder.pauseRecording()
37 | XCTAssertEqual(sessionRecorder.state, SessionRecorderState.Paused)
38 | }
39 |
40 | func testCanFinishRecording() {
41 | sessionRecorder.finishRecording()
42 | XCTAssertEqual(sessionRecorder.state, SessionRecorderState.Finished)
43 | }
44 |
45 | func testCanCreateNewSession() {
46 | sessionRecorder.newSession()
47 | XCTAssertNotNil(sessionRecorder.currentSession)
48 | }
49 |
50 | func testCanAddValueToSessionIfRecording() {
51 | sessionRecorder.startRecording()
52 | sessionRecorder.newSession()
53 | sessionRecorder.addValue(1)
54 | let sessionLength = sessionRecorder.currentSession!.values?.count
55 | XCTAssertEqual(sessionLength, 1)
56 | }
57 |
58 | func testDoesNotAddValueIfPaused() {
59 | sessionRecorder.newSession()
60 | sessionRecorder.pauseRecording()
61 | sessionRecorder.addValue(1)
62 | let sessionLength = sessionRecorder.currentSession!.values?.count
63 | XCTAssertEqual(sessionLength, 0)
64 | }
65 |
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/BeatsTests/HeartRateRecorderIntegrationTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HeartRateRecorderIntegrationTests.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 04/02/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Beats
11 |
12 | class HeartRateRecorderIntegrationTests: XCTestCase {
13 |
14 | var heartRateVC: HeartRateViewController!
15 | var sessionRecorder: SessionRecorder!
16 |
17 | override func setUp() {
18 | super.setUp()
19 |
20 | let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
21 | heartRateVC = storyboard.instantiateViewControllerWithIdentifier("HeartRateViewController") as! HeartRateViewController
22 | UIApplication.sharedApplication().keyWindow!.rootViewController = heartRateVC
23 |
24 | XCTAssertNotNil(heartRateVC.view)
25 |
26 | heartRateVC.heartRateKit?.mode = .Demo
27 | heartRateVC.heartRateKit?.scanForMonitors()
28 | sessionRecorder = SessionRecorder.sharedInstance
29 | }
30 |
31 | override func tearDown() {
32 | super.tearDown()
33 | }
34 |
35 | func testHeartRateIsRecorded() {
36 | weak var expectation = expectationWithDescription("Heart rate should be saved to session")
37 |
38 | let completion = { () -> Void in
39 | let valueCount = self.sessionRecorder.currentSession?.values?.count
40 | XCTAssertTrue(valueCount > 2)
41 | expectation?.fulfill()
42 | }
43 |
44 | heartRateVC.recordingControlsVC?.startRecording()
45 | asyncTest(completion, wait: 4)
46 |
47 | waitForExpectationsWithTimeout(5.5, handler: nil)
48 | }
49 |
50 | func testSessionIsEndedWhenFinishButtonTapped() {
51 | heartRateVC.recordingControlsVC?.startRecording()
52 | heartRateVC.recordingControlsVC?.finishRecording()
53 | XCTAssertEqual(sessionRecorder.state, SessionRecorderState.Finished)
54 | XCTAssertNotNil(sessionRecorder.currentSession?.endTime)
55 | }
56 |
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/Beats/MockCentralManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockCentralManager.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 08/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreBluetooth
11 |
12 | final class MockCentralManager: NSObject {
13 |
14 | var delegate: MockCentralManagerDelegate?
15 |
16 | var scanTimer: NSTimer?
17 |
18 | init(delegate: MockCentralManagerDelegate?, queue: dispatch_queue_t?) {
19 | self.delegate = delegate
20 | super.init()
21 | }
22 |
23 | func scanForPeripheralsWithServices(serviceUUIDs: [CBUUID]?, options: [String : AnyObject]?) {
24 | scanForPeripheralsWithServicesCalled = true
25 |
26 | scanTimer = NSTimer.scheduledTimerWithTimeInterval(1.5, target: self, selector: "discoverPeripheral", userInfo: nil, repeats: false)
27 | }
28 |
29 | func connectPeripheral(peripheral: MockPeripheral, options: [String: AnyObject]?) {
30 | connectedToPeripheral = true
31 |
32 | let timerArray = [peripheral]
33 | _ = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "connectPeripheral:", userInfo: timerArray, repeats: false)
34 | }
35 |
36 | // MARK: Helper methods
37 |
38 | func discoverPeripheral() {
39 | discoveredPeripheral = true
40 |
41 | let peripheral = MockPeripheral()
42 | delegate?.centralManager(self, didDiscoverPeripheral: peripheral, advertisementData: ["CBAdvertisementDataLocalNameKey": "MockPolarH7"], RSSI: 42)
43 | }
44 |
45 | func connectPeripheral(timer: NSTimer?) {
46 | guard
47 | let timer = timer,
48 | let userInfo = timer.userInfo,
49 | let peripheral = userInfo[0] as? MockPeripheral
50 | else { return }
51 | delegate?.centralManager(self, didConnectPeripheral: peripheral)
52 | timer.invalidate()
53 | }
54 |
55 | // MARK: Method Flags
56 |
57 | var scanForPeripheralsWithServicesCalled = false
58 | var discoveredPeripheral = false
59 | var connectedToPeripheral = true
60 |
61 | }
--------------------------------------------------------------------------------
/Beats/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 08/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(application: UIApplication) {
33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/Beats/HeartRateKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HeartRateKit.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 08/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | final class HeartRateKit : NSObject, BluetoothControllerDelegate {
12 |
13 | static let sharedInstance = HeartRateKit()
14 |
15 | var bluetoothController: BluetoothControllerProtocol?
16 | var uiDelegate: HeartRateKitUIDelegate?
17 |
18 | var availableDeviceNames = [String]()
19 | var currentHeartRate: Int?
20 |
21 | var state: HeartRateKitState {
22 | didSet {
23 | uiDelegate?.hrKitDidUpdateState(state)
24 | }
25 | }
26 |
27 | var mode: HeartRateKitMode {
28 | didSet {
29 | switch mode {
30 | case .Inactive:
31 | break
32 | case .Bluetooth:
33 | bluetoothController = BluetoothController()
34 | bluetoothController?.delegate = self
35 | case .Demo:
36 | bluetoothController = MockBluetoothController()
37 | bluetoothController?.delegate = self
38 | }
39 | }
40 | }
41 |
42 | private override init() {
43 | state = .Inactive
44 | mode = .Inactive
45 | super.init()
46 | }
47 |
48 | func scanForMonitors() {
49 | bluetoothController!.scanForAvailableMonitors()
50 | }
51 |
52 |
53 |
54 | // MARK: BluetoothControllerDelegate
55 |
56 | func bluetooothControllerStateChanged(state: BluetoothControllerState) {
57 | switch state {
58 | case .Scanning:
59 | self.state = .Scanning
60 | case .FoundMonitor:
61 | self.state = .FoundMonitor
62 | case .ConnectedMonitor:
63 | self.state = .Connected
64 | default:
65 | break
66 | }
67 | }
68 |
69 | func heartRateUpdated(hr: Int) {
70 | currentHeartRate = hr
71 | uiDelegate?.hrKitDidUpdateBPM(hr)
72 | }
73 |
74 | }
75 |
76 |
77 | enum HeartRateKitMode {
78 | case Inactive
79 | case Bluetooth
80 | case Demo
81 | }
82 |
83 | enum HeartRateKitState {
84 | case Inactive
85 | case Scanning
86 | case FoundMonitor
87 | case Connected
88 | }
89 |
--------------------------------------------------------------------------------
/Beats/RecordingControlsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RecordingControlsViewController.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 28/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class RecordingControlsViewController: UIViewController, SessionRecorderDelegate {
12 |
13 | var sessionRecorder: SessionRecorder?
14 |
15 | @IBOutlet weak var stackView: UIStackView!
16 |
17 | @IBOutlet weak var startButton: UIButton!
18 | @IBOutlet weak var stopButton: UIButton!
19 | @IBOutlet weak var finishButton: UIButton!
20 |
21 | override func viewDidLoad() {
22 | sessionRecorder = SessionRecorder.sharedInstance
23 | sessionRecorder?.delegate = self
24 | sessionRecorder?.state = .Inactive
25 | }
26 |
27 | func startRecording() {
28 | guard let sessionRecorder = sessionRecorder else { return }
29 | sessionRecorder.startRecording()
30 | }
31 |
32 | func pauseRecording() {
33 | guard let sessionRecorder = sessionRecorder else { return }
34 | sessionRecorder.pauseRecording()
35 | }
36 |
37 | func finishRecording() {
38 | guard let sessionRecorder = sessionRecorder else { return }
39 | sessionRecorder.finishRecording()
40 | }
41 |
42 | @IBAction func buttonTapped(sender: UIButton) {
43 | switch sender {
44 | case startButton:
45 | startRecording()
46 | case stopButton:
47 | pauseRecording()
48 | case finishButton:
49 | finishRecording()
50 | default:
51 | break
52 | }
53 | }
54 |
55 | func recorderDidUpdateState(state: SessionRecorderState) {
56 | switch state {
57 | case .Inactive:
58 | startButton.hidden = false
59 | finishButton.hidden = true
60 | stopButton.hidden = true
61 | case .Recording:
62 | startButton.hidden = true
63 | finishButton.hidden = true
64 | stopButton.hidden = false
65 | case .Paused:
66 | startButton.hidden = false
67 | finishButton.hidden = false
68 | stopButton.hidden = true
69 | default:
70 | break
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/BeatsTests/MockBluetoothControllerPeripheralTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockBluetoothControllerPeripheralTests.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 17/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | /* weak var used for expectations to avoid crash if expectation is fulfilled after wait period
10 |
11 | http://stackoverflow.com/questions/27555499/xctestexpectation-how-to-avoid-calling-the-fulfill-method-after-the-wait-contex
12 | */
13 |
14 | import XCTest
15 | @testable import Beats
16 |
17 | class MockBluetoothControllerPeripheralTests: XCTestCase {
18 |
19 | var mockBluetoothController: MockBluetoothController!
20 | let mockPeripheral = MockPeripheral()
21 |
22 | override func setUp() {
23 | mockBluetoothController = MockBluetoothController()
24 | mockBluetoothController.centralManager = MockCentralManager(delegate: mockBluetoothController, queue: nil)
25 | mockBluetoothController.centralManager(self.mockBluetoothController.centralManager!, didConnectPeripheral: mockPeripheral)
26 | super.setUp()
27 | }
28 |
29 | override func tearDown() {
30 | super.tearDown()
31 | }
32 |
33 | func testStateIsConnected() {
34 | let state = mockBluetoothController.state
35 | XCTAssertEqual(state, BluetoothControllerState.ConnectedMonitor)
36 | }
37 |
38 | func testCanBePeripheralsDelegate() {
39 | let delegate = mockPeripheral.delegate as! MockBluetoothController
40 | XCTAssertEqual(delegate, mockBluetoothController)
41 | }
42 |
43 | func testOnConnectionDidDiscoverServices() {
44 | XCTAssertTrue(mockBluetoothController.didDiscoverServicesCalled)
45 | }
46 |
47 | func testIfPeripheralHasHeartRateServiceDiscoverCharacteristics() {
48 | XCTAssertTrue(mockBluetoothController.didDiscoverCharacteristicsCalled)
49 | }
50 |
51 | func testReceivesUpdatesOnHeartRate() {
52 | weak var expectation = expectationWithDescription("Should see a pulse")
53 |
54 | let completion = { () -> Void in
55 | XCTAssertTrue(self.mockBluetoothController.hrNotificationReceived)
56 | expectation?.fulfill()
57 | }
58 |
59 | mockPeripheral.setHeartRateMode(.SteadyResting)
60 | asyncTest(completion, wait: 4)
61 |
62 | waitForExpectationsWithTimeout(6.5, handler: nil)
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/Beats/ScanningViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScanningViewController.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 20/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class ScanningViewController : UIViewController, HeartRateKitUIDelegate {
12 |
13 | var state: UIState?
14 | var heartRateKit: HeartRateKit?
15 |
16 | @IBOutlet weak var titleLabel: UILabel!
17 | @IBOutlet weak var subtitleLabel: UILabel!
18 | @IBOutlet weak var centralImage: UIImageView!
19 | @IBOutlet weak var demoButton: UIButton!
20 |
21 |
22 |
23 | override func viewDidLoad() {
24 | state = .Searching
25 | heartRateKit = HeartRateKit.sharedInstance
26 | heartRateKit?.uiDelegate = self
27 | heartRateKit?.mode = .Bluetooth
28 | }
29 |
30 | func setToDemoMode() {
31 | heartRateKit?.mode = .Demo
32 | heartRateKit?.scanForMonitors()
33 | }
34 |
35 | func hrKitDidUpdateState(state: HeartRateKitState) {
36 | switch state {
37 | case .Connected:
38 | self.state = .Connected
39 | updateUI()
40 | default:
41 | break
42 | }
43 | }
44 |
45 | func hrKitDidUpdateBPM(bpm: Int) {
46 | if !segueToHeartRateTriggered { self.transitionToNextView() }
47 |
48 | }
49 |
50 | func updateUI() {
51 | switch self.state! {
52 | case .Searching:
53 | break
54 | case .Connected:
55 | connectedUI()
56 | }
57 | }
58 |
59 | func connectedUI() {
60 | view.backgroundColor = UIColor(red: 74/255, green: 198/255, blue: 183/255, alpha: 1)
61 | centralImage.image = UIImage(named: "tick")
62 | demoButton.hidden = true
63 | titleLabel.text = "Connected"
64 | if heartRateKit?.mode == .Demo {
65 | subtitleLabel.text = "In demo mode"
66 | } else {
67 | subtitleLabel.text = ""
68 | }
69 | }
70 |
71 | @IBAction func demoButtonTapped(sender: AnyObject) {
72 | setToDemoMode()
73 | }
74 |
75 | func transitionToNextView() {
76 | segueCount++
77 | self.segueToHeartRateTriggered = true
78 |
79 | let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), 2 * Int64(NSEC_PER_SEC))
80 | dispatch_after(time, dispatch_get_main_queue()) {
81 | self.performSegueWithIdentifier("SegueToHeartRate", sender: self)
82 | }
83 | }
84 |
85 |
86 | // MARK: Testing flags
87 |
88 | var segueToHeartRateTriggered = false
89 | var segueCount = 0
90 | }
91 |
92 | enum UIState {
93 | case Searching
94 | case Connected
95 | }
96 |
--------------------------------------------------------------------------------
/BeatsTests/MockPeripheralTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockPeripheralTests.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 11/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import CoreBluetooth
11 |
12 | @testable import Beats
13 |
14 | class MockPeripheralTests: XCTestCase {
15 |
16 | var mockPeripheral: MockPeripheral!
17 |
18 | override func setUp() {
19 | mockPeripheral = MockPeripheral()
20 | mockPeripheral.delegate = MockBluetoothController()
21 | mockPeripheral.discoverServices(nil)
22 | super.setUp()
23 | }
24 |
25 | override func tearDown() {
26 | super.tearDown()
27 | }
28 |
29 | func testCanDiscoverServices() {
30 | XCTAssertTrue(mockPeripheral.didDiscoverServicesCalled)
31 | }
32 |
33 | func testHasHeartRateServiceCBUUID() {
34 | XCTAssertNotNil(mockPeripheral.services)
35 | }
36 |
37 | func testCanReturnHRService() {
38 | let result = mockPeripheral.getHeartRateService()
39 | XCTAssertNotNil(result)
40 | }
41 |
42 | func testCanReturnHRMeasurementCharacteristic() {
43 | let result = mockPeripheral.getHeartRateMeasurementCharacteristic()
44 | XCTAssertNotNil(result)
45 | }
46 |
47 | func testCanChangeNotifyValueForCharacteristic() {
48 | let char = mockPeripheral.getHeartRateMeasurementCharacteristic()
49 | mockPeripheral.setNotifyValue(true, forCharacteristic: char!)
50 | XCTAssertTrue(mockPeripheral.notifyOnHRUpdate)
51 | }
52 |
53 | func testCanSetHeartRateMode() {
54 | mockPeripheral.setHeartRateMode(.SteadyResting)
55 | XCTAssertNotNil(mockPeripheral.availableHRs)
56 | }
57 |
58 | func testCanStartPulse() {
59 | weak var expectation = expectationWithDescription("Pulse should start")
60 |
61 | let completion = { () -> Void in
62 | XCTAssertTrue(self.mockPeripheral.updateHRCalled)
63 | expectation?.fulfill()
64 | }
65 |
66 | mockPeripheral.setHeartRateMode(.SteadyResting)
67 | asyncTest(completion, wait: 1)
68 |
69 | waitForExpectationsWithTimeout(1.5, handler: nil)
70 | }
71 |
72 | func testIfNotifyOnHRSetWillUpdateDelegate() {
73 | weak var expectation = expectationWithDescription("Delegate should be updated")
74 |
75 | let completion = { () -> Void in
76 | let btc = self.mockPeripheral.delegate as! MockBluetoothController
77 | XCTAssertTrue(btc.hrNotificationReceived)
78 | expectation?.fulfill()
79 | }
80 |
81 | mockPeripheral.setHeartRateMode(.SteadyResting)
82 | asyncTest(completion, wait: 1)
83 |
84 | waitForExpectationsWithTimeout(1.5, handler: nil)
85 | }
86 |
87 | }
88 |
89 |
90 |
--------------------------------------------------------------------------------
/BeatsTests/MockBluetoothControllerConnectionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockBluetoothControllerConnectionTests.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 08/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | @testable import Beats
12 |
13 | class MockBluetoothControllerConnectionTests: XCTestCase {
14 |
15 | var mockBluetoothController: MockBluetoothController!
16 | let mockPeripheral = MockPeripheral()
17 |
18 | override func setUp() {
19 | mockBluetoothController = MockBluetoothController()
20 | super.setUp()
21 | }
22 |
23 | override func tearDown() {
24 | super.tearDown()
25 | }
26 |
27 | func testCanInitializeWithCentralManager() {
28 | XCTAssertNotNil(mockBluetoothController.centralManager)
29 | }
30 |
31 | func testHasState() {
32 | XCTAssertEqual(mockBluetoothController.state, BluetoothControllerState.StartedUp)
33 | }
34 |
35 | func testScanForAvailableMonitorsChangesState() {
36 | mockBluetoothController.scanForAvailableMonitors()
37 | let state = mockBluetoothController.state
38 |
39 | XCTAssertEqual(state, BluetoothControllerState.Scanning)
40 | }
41 |
42 | func testOnScanForAvailableMonitorsCentralManagerScanMethodCalled() {
43 | mockBluetoothController.scanForAvailableMonitors()
44 | XCTAssertTrue(mockBluetoothController!.centralManager!.scanForPeripheralsWithServicesCalled)
45 | }
46 |
47 | func testOnScanForAvailableMonitorsPeripheralIsFound() {
48 | weak var expectation = expectationWithDescription("didDiscoverPeripheral should be called")
49 |
50 | let completion = { () -> Void in
51 | XCTAssertTrue(self.mockBluetoothController.didDiscoverPeripheralCalled)
52 | expectation?.fulfill()
53 | }
54 |
55 | mockBluetoothController.scanForAvailableMonitors()
56 |
57 | asyncTest(completion, wait: 3)
58 |
59 | waitForExpectationsWithTimeout(3.5, handler: nil)
60 | }
61 |
62 | func testWhenPeripheralFoundConnected() {
63 | weak var expectation = expectationWithDescription("Should connect to peripheral when found")
64 |
65 | let completion = { () -> Void in
66 | XCTAssertTrue(self.mockBluetoothController.didConnectPeripheralCalled)
67 | expectation?.fulfill()
68 | }
69 |
70 | mockBluetoothController.scanForAvailableMonitors()
71 |
72 | asyncTest(completion, wait: 4)
73 |
74 | waitForExpectationsWithTimeout(4.5, handler: nil)
75 | }
76 |
77 | func testStateChangeToConnected() {
78 | mockBluetoothController.centralManager(self.mockBluetoothController.centralManager!, didConnectPeripheral: mockPeripheral)
79 |
80 | let state = self.mockBluetoothController.state
81 |
82 | XCTAssertEqual(state, BluetoothControllerState.ConnectedMonitor)
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/BeatsTests/HeartRateKitTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HeartRateKitTests.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 08/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | @testable import Beats
12 |
13 | class HeartRateKitTests: XCTestCase {
14 |
15 | var heartRateKit: HeartRateKit!
16 |
17 | override func setUp() {
18 | heartRateKit = HeartRateKit.sharedInstance
19 | heartRateKit.mode = .Demo
20 | heartRateKit.bluetoothController!.delegate = heartRateKit
21 | super.setUp()
22 | }
23 |
24 | override func tearDown() {
25 | super.tearDown()
26 | }
27 |
28 | func testHasABluetoothController() {
29 | XCTAssertNotNil(heartRateKit.bluetoothController)
30 | }
31 |
32 | func testCanTryConnectToHeartRateMonitor() {
33 | heartRateKit.scanForMonitors()
34 | let state = heartRateKit.state
35 | XCTAssertEqual(state, HeartRateKitState.Scanning)
36 | }
37 |
38 | func testStateChangeWhenDeviceFound() {
39 | weak var expectation = expectationWithDescription("State will change to .FoundMonitor")
40 |
41 | let completionBlock = { () -> Void in
42 | let state = self.heartRateKit.state
43 | XCTAssertEqual(state, HeartRateKitState.FoundMonitor)
44 | expectation?.fulfill()
45 | }
46 |
47 | heartRateKit.scanForMonitors()
48 |
49 | asyncTest(completionBlock, wait: 2)
50 |
51 | waitForExpectationsWithTimeout(2.5, handler: nil)
52 | }
53 |
54 | func testCanConnectToAvailableMonitor() {
55 | weak var expectation = expectationWithDescription("Should connect to available monitor")
56 |
57 | let completionBlock = { () -> Void in
58 | let state = self.heartRateKit.state
59 | XCTAssertEqual(state, HeartRateKitState.Connected)
60 | expectation?.fulfill()
61 | }
62 |
63 | heartRateKit.scanForMonitors()
64 |
65 | asyncTest(completionBlock, wait: 4)
66 |
67 | waitForExpectationsWithTimeout(4.0, handler: nil)
68 | }
69 |
70 | func testCanReceiveHeartRate() {
71 | weak var expectation = expectationWithDescription("Should receive a heart rate from Bluetooth Controller")
72 |
73 | let completionBlock = { () -> Void in
74 | XCTAssertNotNil(self.heartRateKit.currentHeartRate)
75 | XCTAssertTrue(self.heartRateKit.currentHeartRate > 50)
76 | expectation?.fulfill()
77 | }
78 |
79 | heartRateKit.scanForMonitors()
80 |
81 | asyncTest(completionBlock, wait: 5)
82 |
83 | waitForExpectationsWithTimeout(5.5, handler: nil)
84 | }
85 |
86 | func testCanBeSetToDemoMode() {
87 | heartRateKit.mode = .Bluetooth
88 | XCTAssertEqual(heartRateKit.mode, HeartRateKitMode.Bluetooth)
89 | heartRateKit.mode = .Demo
90 | let controller = heartRateKit.bluetoothController as? MockBluetoothController
91 | XCTAssertNotNil(controller)
92 | }
93 |
94 | }
--------------------------------------------------------------------------------
/BeatsTests/ScanningViewControllerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScanningViewControllerTests.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 19/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | @testable import Beats
12 |
13 | class ScanningViewControllerTests: XCTestCase {
14 |
15 | var scanningVC: ScanningViewController!
16 |
17 | override func setUp() {
18 | super.setUp()
19 |
20 | let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
21 | scanningVC = storyboard.instantiateViewControllerWithIdentifier("ScanningViewController") as! ScanningViewController
22 | UIApplication.sharedApplication().keyWindow!.rootViewController = scanningVC
23 |
24 | XCTAssertNotNil(scanningVC.view)
25 | }
26 |
27 | override func tearDown() {
28 | super.tearDown()
29 | }
30 |
31 | func testHasState() {
32 | XCTAssertNotNil(scanningVC.state)
33 | }
34 |
35 | func testHasHeartRateKit() {
36 | XCTAssertNotNil(scanningVC.heartRateKit)
37 | }
38 |
39 | func testCanSetHRKitToDemo() {
40 | scanningVC.setToDemoMode()
41 | XCTAssertEqual(scanningVC.heartRateKit!.mode, HeartRateKitMode.Demo)
42 | }
43 |
44 | func testCanUpdateVCStateWhenHRKitStateChanges() {
45 | scanningVC.setToDemoMode()
46 | scanningVC.heartRateKit?.state = .Connected
47 | XCTAssertEqual(self.scanningVC.state, UIState.Connected)
48 | }
49 |
50 | func testUpdatesUIOnStateChange() {
51 | let initialImage = scanningVC.centralImage.image
52 | XCTAssertEqual(initialImage, UIImage(named: "ellipses"))
53 | scanningVC.setToDemoMode()
54 | scanningVC.heartRateKit?.state = .Connected
55 | let secondImage = scanningVC.centralImage.image
56 | XCTAssertEqual(secondImage, UIImage(named: "tick"))
57 | }
58 |
59 | func testLeavesScanningScreenAfterConnected() {
60 | weak var expectation = expectationWithDescription("Should leave screen on connection")
61 |
62 | let completion = { () -> Void in
63 | XCTAssertTrue(self.scanningVC.segueToHeartRateTriggered)
64 | expectation?.fulfill()
65 | }
66 |
67 | scanningVC.setToDemoMode()
68 | scanningVC.heartRateKit?.state = .Connected
69 |
70 | asyncTest(completion, wait: 5)
71 |
72 | waitForExpectationsWithTimeout(5.5, handler: nil)
73 | }
74 |
75 | func testOnlyTransitionsToNextViewOnce() {
76 | weak var expectation = expectationWithDescription("Should only transition once")
77 |
78 | let completion = { () -> Void in
79 | let count = self.scanningVC.segueCount
80 | print("Count: \(count)")
81 | XCTAssertTrue(count == 1)
82 | expectation?.fulfill()
83 | }
84 |
85 | scanningVC.setToDemoMode()
86 | scanningVC.heartRateKit?.state = .Connected
87 |
88 | asyncTest(completion, wait: 5)
89 |
90 | waitForExpectationsWithTimeout(5.5, handler: nil)
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/BeatsTests/RecordingControlsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RecordingControlsViewController.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 28/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | @testable import Beats
12 |
13 | class RecordingControlsViewControllerTests : XCTestCase {
14 |
15 | var recordingControlsVC: RecordingControlsViewController!
16 | var recorder: SessionRecorder!
17 |
18 |
19 | override func setUp() {
20 | super.setUp()
21 |
22 | let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
23 | recordingControlsVC = storyboard.instantiateViewControllerWithIdentifier("RecordingControlsViewController") as! RecordingControlsViewController
24 | UIApplication.sharedApplication().keyWindow!.rootViewController = recordingControlsVC
25 |
26 | XCTAssertNotNil(recordingControlsVC.view)
27 |
28 | recorder = recordingControlsVC.sessionRecorder
29 | }
30 |
31 | override func tearDown() {
32 | super.tearDown()
33 | }
34 |
35 | func testHasASessionRecorder() {
36 | XCTAssertNotNil(recordingControlsVC.sessionRecorder)
37 | }
38 |
39 | func testCanStartRecording() {
40 | recordingControlsVC.startRecording()
41 | XCTAssertEqual(recorder.state, SessionRecorderState.Recording)
42 | }
43 |
44 | func testCanStopRecording() {
45 | recordingControlsVC.pauseRecording()
46 | XCTAssertEqual(recorder.state, SessionRecorderState.Paused)
47 | }
48 |
49 | func testCanFinishRecording() {
50 | recordingControlsVC.finishRecording()
51 | XCTAssertEqual(recorder.state, SessionRecorderState.Finished)
52 | }
53 |
54 | func testButtonTappedControlsRecording() {
55 | recordingControlsVC.buttonTapped(recordingControlsVC.startButton)
56 | XCTAssertEqual(recorder.state, SessionRecorderState.Recording)
57 |
58 | recordingControlsVC.buttonTapped(recordingControlsVC.stopButton)
59 | XCTAssertEqual(recorder.state, SessionRecorderState.Paused)
60 |
61 | recordingControlsVC.buttonTapped(recordingControlsVC.finishButton)
62 | XCTAssertEqual(recorder.state, SessionRecorderState.Finished)
63 | }
64 |
65 | func testIfStateNilRecordButtonShown() {
66 | recorder.state = .Inactive
67 | XCTAssertFalse(recordingControlsVC.startButton.hidden)
68 | XCTAssertTrue(recordingControlsVC.stopButton.hidden)
69 | XCTAssertTrue(recordingControlsVC.finishButton.hidden)
70 | }
71 |
72 | func testIfRecordingStopButtonShown() {
73 | recorder.state = .Recording
74 | XCTAssertTrue(recordingControlsVC.startButton.hidden)
75 | XCTAssertFalse(recordingControlsVC.stopButton.hidden)
76 | XCTAssertTrue(recordingControlsVC.finishButton.hidden)
77 | }
78 |
79 | func testIfPauseRecordingStartFinishButtonsShown() {
80 | recorder.state = .Paused
81 | XCTAssertFalse(recordingControlsVC.startButton.hidden)
82 | XCTAssertTrue(recordingControlsVC.stopButton.hidden)
83 | XCTAssertFalse(recordingControlsVC.finishButton.hidden)
84 | }
85 |
86 | }
87 |
88 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Beats
2 |
3 | A heart rate app for iOS for stationary workouts (treadmill, circuit classes, etc.). Built because I couldn't find a nicely designed, simple heart rate tracker for workouts that didn't involved tracking distance rather than time.
4 |
5 | 
6 |
7 | Currently tested and compatible with:
8 | * Polar H7 Heart Rate Monitor
9 | * More to follow ...
10 |
11 | ### Technology
12 | * Swift 2
13 | * Xcode 7
14 | * XCTest
15 | * UITesting
16 | * Core Bluetooth
17 |
18 | One of the biggest technical challenges so far has been creating a mock version of the Core Bluetooth API, made particularly difficult as key objects have no public initializers.
19 |
20 | Designed using Sketch.
21 |
22 | ### To Do
23 | For full development roadmap, head over to the project board [Trello](https://trello.com/b/PrwzpYqY/beats).
24 |
25 | Future plans and features include:
26 |
27 | * Record workout
28 | * Save workout
29 | * Display workout on scrolling graph
30 | * Pull HeartRateKit out into a framework
31 | * Personalised heart rate zones
32 |
33 |
34 | ### Resources
35 | * [**Core Bluetooth Programming Guide**](https://developer.apple.com/library/ios/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/AboutCoreBluetooth/Introduction.html)
36 |
37 | * [**Core Bluetooth Framework Reference**](https://developer.apple.com/library/ios/documentation/CoreBluetooth/Reference/CoreBluetooth_Framework/index.html#//apple_ref/doc/uid/TP40011295)
38 |
39 | * [**Polar Developer Resources**](http://developer.polar.com/wiki/H6_and_H7_Heart_rate_sensors)
40 |
41 | * [**Bluetooth Developer Portal**](https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.heart_rate.xml): Guidance on standards for heart rate measurement services and characteristics.
42 |
43 | * [**RZBluetooth**](https://github.com/Raizlabs/RZBluetooth): An open source Objective-C wrapper for Core Bluetooth. Used for inspiration to understand how to test and mock Core Bluetooth. Also I submitted pull request to add Swift example code.
44 |
45 | ### Code Snippet
46 |
47 | A helper method to run asynchronous tests on methods that don't have a closure in the argument. The only example code for XCTestExpectation was for networking code, like [this from Big Nerd Ranch](https://www.bignerdranch.com/blog/asynchronous-testing-with-xcode-6/), but I need to test CoreBluetooth methods which did not include a completionHandler in the method signature. In order to avoid altering the CoreBluetooth code purely for testing purposes I wrote this `asyncTest` function which is used throughout my tests.
48 |
49 | ```swift
50 | extension XCTestCase {
51 |
52 | func asyncTest(completion: () -> Void, wait: Int64){
53 | let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), wait * Int64(NSEC_PER_SEC))
54 |
55 | dispatch_after(time, dispatch_get_main_queue()) {
56 | completion()
57 | }
58 | }
59 |
60 | }
61 | ```
62 |
63 | Example usage:
64 |
65 | ```swift
66 | func testReceivesUpdatesOnHeartRate() {
67 | weak var expectation = expectationWithDescription("Should see a pulse")
68 |
69 | let completion = { () -> Void in
70 | XCTAssertTrue(self.mockBluetoothController.hrNotificationReceived)
71 | expectation?.fulfill()
72 | }
73 |
74 | mockPeripheral.setHeartRateMode(.SteadyResting)
75 | asyncTest(completion, wait: 5)
76 |
77 | waitForExpectationsWithTimeout(5.5, handler: nil)
78 | }
79 | ```
80 |
--------------------------------------------------------------------------------
/Beats/MockBluetoothController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockBluetoothController.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 08/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import CoreBluetooth
10 |
11 | final class MockBluetoothController: NSObject, BluetoothControllerProtocol, MockCentralManagerDelegate, MockPeripheralDelegate {
12 |
13 | var state: BluetoothControllerState {
14 | didSet {
15 | delegate?.bluetooothControllerStateChanged(state)
16 | }
17 | }
18 |
19 | var centralManager: MockCentralManager?
20 | var delegate: BluetoothControllerDelegate?
21 |
22 |
23 | override init() {
24 | state = .StartedUp
25 | super.init()
26 | centralManager = MockCentralManager(delegate: self, queue: nil)
27 | }
28 |
29 | func scanForAvailableMonitors() {
30 | guard let centralManager = centralManager else { return }
31 | centralManager.scanForPeripheralsWithServices(nil, options: nil)
32 | state = .Scanning
33 | }
34 |
35 |
36 | // MARK: MockCentralManagerDelegate
37 |
38 | func centralManager(central: MockCentralManager, didDiscoverPeripheral peripheral: MockPeripheral,
39 | advertisementData: [String : AnyObject], RSSI: NSNumber) {
40 | didDiscoverPeripheralCalled = true
41 | state = .FoundMonitor
42 | centralManager?.connectPeripheral(peripheral, options: nil)
43 | }
44 |
45 | func centralManager(central: MockCentralManager, didConnectPeripheral peripheral: MockPeripheral) {
46 | didConnectPeripheralCalled = true
47 | state = .ConnectedMonitor
48 | peripheral.delegate = self
49 | peripheral.discoverServices(nil)
50 | }
51 |
52 | // MARK: MockPeripheralDelegate
53 |
54 | func peripheral(peripheral: MockPeripheral, didDiscoverServices error: NSError?) {
55 | didDiscoverServicesCalled = true
56 |
57 | guard let hrService = peripheral.getHeartRateService() else { return }
58 | peripheral.discoverCharacteristics([], forService:hrService)
59 | }
60 |
61 | func peripheral(peripheral: MockPeripheral, didDiscoverCharacteristics error: NSError?) {
62 | didDiscoverCharacteristicsCalled = true
63 |
64 | guard let hrMeasurement = peripheral.getHeartRateMeasurementCharacteristic() else { return }
65 | peripheral.setNotifyValue(true, forCharacteristic: hrMeasurement)
66 | startPulse(peripheral, mode: .SteadyResting)
67 | }
68 |
69 | func peripheral(peripheral: MockPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
70 | hrNotificationReceived = true
71 | guard
72 | let mockChar = characteristic as? MockCharacteristic,
73 | let data = mockChar.mockValue,
74 | let hr = hrDataToInt(data)
75 | else { return }
76 | delegate?.heartRateUpdated(hr)
77 | }
78 |
79 |
80 | // MARK: Mock Heart Rate
81 |
82 | func startPulse(peripheral: MockPeripheral, mode: MockHeartRateMode) {
83 | peripheral.setHeartRateMode(mode)
84 | }
85 |
86 | func hrDataToInt(data: NSData) -> Int? {
87 | var bpm: Int = 0
88 | data.getBytes(&bpm, length: sizeof(Int))
89 | return bpm
90 | }
91 |
92 | // MARK: Method called Flags
93 |
94 | var didDiscoverPeripheralCalled = false
95 | var didConnectPeripheralCalled = false
96 | var didDiscoverServicesCalled = false
97 | var didDiscoverCharacteristicsCalled = false
98 | var hrNotificationReceived = false
99 |
100 | }
101 |
102 |
--------------------------------------------------------------------------------
/Beats.xcodeproj/project.xcworkspace/xcuserdata/yvette.xcuserdatad/xcdebugger/Expressions.xcexplist:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
7 |
8 |
10 |
11 |
12 |
13 |
15 |
16 |
18 |
19 |
20 |
21 |
23 |
24 |
26 |
27 |
28 |
29 |
31 |
32 |
34 |
35 |
36 |
37 |
39 |
40 |
42 |
43 |
44 |
45 |
47 |
48 |
50 |
51 |
52 |
53 |
55 |
56 |
58 |
59 |
60 |
61 |
63 |
64 |
66 |
67 |
68 |
69 |
71 |
72 |
74 |
75 |
76 |
77 |
79 |
80 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/Beats.xcodeproj/xcuserdata/yvette.xcuserdatad/xcschemes/Beats.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
59 |
60 |
61 |
62 |
63 |
64 |
74 |
76 |
82 |
83 |
84 |
85 |
86 |
87 |
93 |
95 |
101 |
102 |
103 |
104 |
106 |
107 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/Beats/MockPeripheral.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockPeripheral.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 11/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import CoreBluetooth
10 |
11 | final class MockPeripheral : NSObject {
12 |
13 | var delegate: MockPeripheralDelegate?
14 |
15 | var services: [CBService]?
16 | var heartRateService: CBService?
17 |
18 | var notifyOnHRUpdate = false
19 |
20 | var availableHRs: [Int]?
21 |
22 | override init() {
23 | self.services = [CBService]()
24 | super.init()
25 | self.heartRateService = createHRService()
26 | }
27 |
28 | func discoverServices(serviceUUIDS: [CBUUID]?) {
29 | didDiscoverServicesCalled = true
30 | guard let service = heartRateService else { return }
31 | services?.append(service)
32 | delegate?.peripheral(self, didDiscoverServices: nil)
33 | }
34 |
35 | func discoverCharacteristics(characteristicUUIDs: [CBUUID]?, forService service: CBService) {
36 | delegate?.peripheral(self, didDiscoverCharacteristics: nil)
37 | }
38 |
39 |
40 | // MARK: Method flags
41 |
42 | var didDiscoverServicesCalled = false
43 | var updateHRCalled = false
44 |
45 |
46 | // MARK: Services
47 |
48 | func createHRService() -> CBService {
49 | let heartRateService = CBMutableService.init(type: CBUUID(string: "180D"), primary: true)
50 |
51 | let measurementUUID = CBUUID(string: "2A37")
52 | let properties = CBCharacteristicProperties.Notify
53 | let permissions = CBAttributePermissions.Writeable
54 |
55 | let heartRateMeasurementCharacteristic = MockCharacteristic.init(type: measurementUUID, properties: properties, permissions: permissions)
56 |
57 | heartRateService.characteristics = [heartRateMeasurementCharacteristic]
58 |
59 | return heartRateService
60 | }
61 |
62 | // MARK: Characteristics
63 |
64 | func setNotifyValue(enabled: Bool, forCharacteristic characteristic: CBCharacteristic) {
65 | if characteristic == getHeartRateMeasurementCharacteristic() {
66 | notifyOnHRUpdate = true
67 | }
68 | }
69 |
70 | func writeValue(data: NSData, forCharacteristic characteristic: CBCharacteristic, type: CBCharacteristicWriteType) {
71 |
72 | // NEED TO WRITE TO THE CHARACTERISTIC'S VALUE HERE
73 |
74 | guard let mockChar = characteristic as? MockCharacteristic else {
75 | print("Not MockCharacteristic")
76 | return
77 | }
78 |
79 | mockChar.mockValue = data
80 | delegate?.peripheral(self, didUpdateValueForCharacteristic: mockChar, error: nil)
81 | }
82 |
83 | }
84 |
85 | extension MockPeripheral {
86 |
87 | // MARK: Heart Rare
88 |
89 | func setHeartRateMode(mode: MockHeartRateMode) {
90 | switch mode {
91 | case .SteadyResting:
92 | let average = 61
93 | availableHRs = [average - 2, average - 1, average, average + 1, average + 2]
94 | startPulse()
95 | }
96 | }
97 |
98 | func startPulse() {
99 | let pulseTimer = NSTimer(timeInterval: 0.5,
100 | target: self,
101 | selector: "updateHR",
102 | userInfo: nil,
103 | repeats: true)
104 |
105 | NSRunLoop.mainRunLoop().addTimer(pulseTimer, forMode: NSRunLoopCommonModes)
106 | }
107 |
108 | func updateHR() {
109 | guard
110 | let currentHeartRate = availableHRs?.randomItem(),
111 | let characteristic = getHeartRateMeasurementCharacteristic()
112 | else { return }
113 |
114 | updateHRCalled = true
115 | let data = heartRateToNSData(currentHeartRate)
116 | writeValue(data, forCharacteristic: characteristic, type: .WithoutResponse)
117 | }
118 |
119 | func heartRateToNSData(var hr: Int) -> NSData {
120 | let data = NSData(bytes: &hr, length: sizeof(Int))
121 | return data
122 | }
123 |
124 | }
125 |
126 | extension MockPeripheral {
127 |
128 | func getHeartRateService() -> CBService? {
129 | guard let services = self.services else { return nil }
130 |
131 | for service in services {
132 | if service.UUID.UUIDString == "180D" { return service }
133 | }
134 |
135 | return nil
136 | }
137 |
138 | func getHeartRateMeasurementCharacteristic() -> CBCharacteristic? {
139 | guard
140 | let hrService = getHeartRateService(),
141 | let characteristics = hrService.characteristics
142 | else { return nil }
143 |
144 | for characteristic in characteristics {
145 | if characteristic.UUID.UUIDString == "2A37" {
146 | return characteristic
147 | }
148 | }
149 |
150 | return nil
151 | }
152 |
153 | }
--------------------------------------------------------------------------------
/Beats/BluetoothController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BluetoothController.swift
3 | // Beats
4 | //
5 | // Created by Yvette Cook on 21/01/2016.
6 | // Copyright © 2016 Yvette. All rights reserved.
7 | //
8 |
9 | import CoreBluetooth
10 |
11 | final class BluetoothController: NSObject, BluetoothControllerProtocol, CBCentralManagerDelegate, CBPeripheralDelegate {
12 |
13 | var state: BluetoothControllerState {
14 | didSet {
15 | print("State did change: \(state)")
16 | delegate?.bluetooothControllerStateChanged(state)
17 | }
18 | }
19 |
20 | var centralManager: CBCentralManager?
21 | var delegate: BluetoothControllerDelegate?
22 |
23 | let heartRateServiceUUID = "180D"
24 | let measurementCharacteristicUUID = "2A37"
25 |
26 | var currentMonitor: CBPeripheral?
27 |
28 |
29 | override init() {
30 | state = .StartedUp
31 | super.init()
32 | centralManager = CBCentralManager(delegate: self, queue: nil, options: nil)
33 | }
34 |
35 | func scanForAvailableMonitors() {
36 | guard let centralManager = centralManager else { return }
37 | centralManager.scanForPeripheralsWithServices([], options: nil)
38 | state = .Scanning
39 | }
40 |
41 |
42 | // MARK: MockCentralManagerDelegate
43 |
44 | func centralManagerDidUpdateState(central: CBCentralManager) {
45 | switch central.state {
46 | case .PoweredOff:
47 | print("CoreBluetooth BLE hardware is powered off")
48 | case .PoweredOn:
49 | print("CoreBluetooth BLE hardware is powered on and ready")
50 | scanForAvailableMonitors()
51 | case .Unauthorized:
52 | print("CoreBluetooth BLE state is unauthorized")
53 | case .Unknown:
54 | print("CoreBluetooth BLE state is unknown")
55 | case .Unsupported:
56 | print("CoreBluetooth BLE hardware is unsupported on this platform")
57 | case .Resetting:
58 | print("CoreBluetooth BLE hardware is resetting")
59 | }
60 | }
61 |
62 | func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral,
63 | advertisementData: [String : AnyObject], RSSI: NSNumber) {
64 | state = .FoundMonitor
65 | self.currentMonitor = peripheral
66 | peripheral.delegate = self
67 | centralManager?.connectPeripheral(peripheral, options: nil)
68 | }
69 |
70 | func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
71 | peripheral.delegate = self
72 | peripheral.discoverServices([CBUUID(string: heartRateServiceUUID)])
73 | }
74 |
75 | func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) {
76 | print("Did disconnect peripheral")
77 | centralManager?.scanForPeripheralsWithServices(nil, options: nil)
78 | }
79 |
80 | // MARK: MockPeripheralDelegate
81 |
82 | func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
83 | print("Peripheral: \(peripheral.name)")
84 |
85 | for service in peripheral.services! {
86 | print("Discovered service: \(service.UUID.UUIDString)")
87 | if service.UUID.UUIDString == heartRateServiceUUID {
88 | peripheral.discoverCharacteristics(nil, forService:service)
89 | state = .ConnectedMonitor
90 | return
91 | }
92 | }
93 |
94 | centralManager?.cancelPeripheralConnection(peripheral)
95 | }
96 |
97 | func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) {
98 | if service.UUID.UUIDString == heartRateServiceUUID {
99 |
100 | for char in service.characteristics! {
101 | if char.UUID.UUIDString == measurementCharacteristicUUID {
102 | self.centralManager?.stopScan()
103 | peripheral.setNotifyValue(true, forCharacteristic: char)
104 | }
105 | }
106 |
107 | } else {
108 | centralManager?.cancelPeripheralConnection(peripheral)
109 | }
110 | }
111 |
112 | func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
113 | if characteristic.UUID.UUIDString == measurementCharacteristicUUID {
114 | self.getHeartBPMData(characteristic, error: error)
115 | }
116 | }
117 |
118 |
119 | // MARK: Heart Rate
120 |
121 | func getHeartBPMData(characteristic: CBCharacteristic, error: NSError?) {
122 | guard let data = characteristic.value else { return }
123 |
124 | let count = data.length / sizeof(UInt8)
125 | var array = [UInt8](count: count, repeatedValue: 0)
126 | data.getBytes(&array, length:count * sizeof(UInt8))
127 |
128 | if ((array[0] & 0x01) == 0) {
129 | let bpm = array[1]
130 | let bpmInt = Int(bpm)
131 | delegate?.heartRateUpdated(bpmInt)
132 | }
133 |
134 | }
135 |
136 |
137 | }
--------------------------------------------------------------------------------
/Beats/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
28 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
101 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
182 |
203 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
--------------------------------------------------------------------------------
/Beats.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1A2D10AB1C4E691B0076F7E4 /* MockCharacteristic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2D10AA1C4E691B0076F7E4 /* MockCharacteristic.swift */; };
11 | 1A2D10B01C4EBABC0076F7E4 /* ScanningViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2D10AF1C4EBABC0076F7E4 /* ScanningViewControllerTests.swift */; };
12 | 1A2D10B31C4F92680076F7E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1A2D10B21C4F92680076F7E4 /* Assets.xcassets */; };
13 | 1A2D10B51C4F99070076F7E4 /* ScanningViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2D10B41C4F99070076F7E4 /* ScanningViewController.swift */; };
14 | 1A2D10B81C4FB8AC0076F7E4 /* HeartRateKitUIDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2D10B71C4FB8AC0076F7E4 /* HeartRateKitUIDelegate.swift */; };
15 | 1A2D10BA1C5012050076F7E4 /* HeartRateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2D10B91C5012050076F7E4 /* HeartRateViewController.swift */; };
16 | 1A2D10BC1C50FFFF0076F7E4 /* HeartRateViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2D10BB1C50FFFF0076F7E4 /* HeartRateViewControllerTests.swift */; };
17 | 1A6EC44B1C512DFF00A042EA /* BluetoothController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A6EC44A1C512DFF00A042EA /* BluetoothController.swift */; };
18 | 1A8F45851C443382002B45B7 /* MockPeripheralTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A8F45841C443382002B45B7 /* MockPeripheralTests.swift */; };
19 | 1A94609B1C5141DD00A0F04B /* BluetoothControllerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A94609A1C5141DD00A0F04B /* BluetoothControllerState.swift */; };
20 | 1A94A8021C638F9F00250E7A /* HeartRateRecorderIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A94A8011C638F9F00250E7A /* HeartRateRecorderIntegrationTests.swift */; };
21 | 1AA255641C40246300A7DAE5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA255631C40246300A7DAE5 /* AppDelegate.swift */; };
22 | 1AA255691C40246300A7DAE5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1AA255671C40246300A7DAE5 /* Main.storyboard */; };
23 | 1AA2556E1C40246300A7DAE5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1AA2556C1C40246300A7DAE5 /* LaunchScreen.storyboard */; };
24 | 1AA255921C4024C400A7DAE5 /* HeartRateKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA255911C4024C400A7DAE5 /* HeartRateKitTests.swift */; };
25 | 1AA255941C4024F900A7DAE5 /* HeartRateKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA255931C4024F900A7DAE5 /* HeartRateKit.swift */; };
26 | 1AA255961C40269D00A7DAE5 /* BluetoothControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA255951C40269D00A7DAE5 /* BluetoothControllerProtocol.swift */; };
27 | 1AA255981C40270B00A7DAE5 /* MockBluetoothController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA255971C40270B00A7DAE5 /* MockBluetoothController.swift */; };
28 | 1AA2559C1C4029CC00A7DAE5 /* MockCentralManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA2559B1C4029CC00A7DAE5 /* MockCentralManager.swift */; };
29 | 1AA2559E1C42E6E600A7DAE5 /* MockCentralManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA2559D1C42E6E600A7DAE5 /* MockCentralManagerTests.swift */; };
30 | 1AA255A41C42F0C500A7DAE5 /* BluetoothControllerDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA255A31C42F0C500A7DAE5 /* BluetoothControllerDelegateProtocol.swift */; };
31 | 1AA255A61C42F19700A7DAE5 /* MockCentralManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA255A51C42F19700A7DAE5 /* MockCentralManagerDelegate.swift */; };
32 | 1AA5BD991C5A3B4200F0369B /* RecordingControlsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA5BD981C5A3B4200F0369B /* RecordingControlsViewController.swift */; };
33 | 1AA5BD9B1C5A403700F0369B /* RecordingControlsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA5BD9A1C5A403700F0369B /* RecordingControlsViewController.swift */; };
34 | 1AA5BD9E1C5A4A3100F0369B /* SessionRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA5BD9D1C5A4A3100F0369B /* SessionRecorder.swift */; };
35 | 1AA5BDA11C5A4C1100F0369B /* SessionRecorderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA5BDA01C5A4C1100F0369B /* SessionRecorderTests.swift */; };
36 | 1AA5BDA31C5B8C0600F0369B /* SessionRecorderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA5BDA21C5B8C0600F0369B /* SessionRecorderDelegate.swift */; };
37 | 1AA5BDA51C5BA5ED00F0369B /* UIButtonWithAspectFit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA5BDA41C5BA5ED00F0369B /* UIButtonWithAspectFit.swift */; };
38 | 1AB1B5DA1C43CD3100AF8A6D /* MockPeripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB1B5D91C43CD3100AF8A6D /* MockPeripheral.swift */; };
39 | 1AC766B61C612CAD00F5E1D4 /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AC766B51C612CAD00F5E1D4 /* Session.swift */; };
40 | 1AC766B81C61360500F5E1D4 /* SessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AC766B71C61360500F5E1D4 /* SessionTests.swift */; };
41 | 1AC766BA1C613DA500F5E1D4 /* Record.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AC766B91C613DA500F5E1D4 /* Record.swift */; };
42 | 1AC86D281C62666A00C92C70 /* RecordTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AC86D271C62666A00C92C70 /* RecordTests.swift */; };
43 | 1AE2DD301C4936FD00FB395E /* UITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE2DD2F1C4936FD00FB395E /* UITest.swift */; };
44 | 1AE2DD321C4BBE9200FB395E /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE2DD311C4BBE9200FB395E /* TestHelpers.swift */; };
45 | 1AE2DD341C4BDBD000FB395E /* MockBluetoothControllerConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE2DD331C4BDBD000FB395E /* MockBluetoothControllerConnectionTests.swift */; };
46 | 1AE2DD361C4BDBFD00FB395E /* MockBluetoothControllerPeripheralTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE2DD351C4BDBFD00FB395E /* MockBluetoothControllerPeripheralTests.swift */; };
47 | 1AE2DD3C1C4C06B400FB395E /* MockPeripheralDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE2DD3B1C4C06B400FB395E /* MockPeripheralDelegate.swift */; };
48 | 1AE2DD401C4E57C500FB395E /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE2DD3F1C4E57C500FB395E /* ArrayExtension.swift */; };
49 | 1AE2DD431C4E646E00FB395E /* MockHeartRateMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE2DD421C4E646E00FB395E /* MockHeartRateMode.swift */; };
50 | /* End PBXBuildFile section */
51 |
52 | /* Begin PBXContainerItemProxy section */
53 | 1AA255751C40246300A7DAE5 /* PBXContainerItemProxy */ = {
54 | isa = PBXContainerItemProxy;
55 | containerPortal = 1AA255581C40246300A7DAE5 /* Project object */;
56 | proxyType = 1;
57 | remoteGlobalIDString = 1AA2555F1C40246300A7DAE5;
58 | remoteInfo = Beats;
59 | };
60 | 1AA255801C40246300A7DAE5 /* PBXContainerItemProxy */ = {
61 | isa = PBXContainerItemProxy;
62 | containerPortal = 1AA255581C40246300A7DAE5 /* Project object */;
63 | proxyType = 1;
64 | remoteGlobalIDString = 1AA2555F1C40246300A7DAE5;
65 | remoteInfo = Beats;
66 | };
67 | /* End PBXContainerItemProxy section */
68 |
69 | /* Begin PBXFileReference section */
70 | 1A2D10AA1C4E691B0076F7E4 /* MockCharacteristic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockCharacteristic.swift; sourceTree = ""; };
71 | 1A2D10AF1C4EBABC0076F7E4 /* ScanningViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanningViewControllerTests.swift; sourceTree = ""; };
72 | 1A2D10B21C4F92680076F7E4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
73 | 1A2D10B41C4F99070076F7E4 /* ScanningViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanningViewController.swift; sourceTree = ""; };
74 | 1A2D10B71C4FB8AC0076F7E4 /* HeartRateKitUIDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeartRateKitUIDelegate.swift; sourceTree = ""; };
75 | 1A2D10B91C5012050076F7E4 /* HeartRateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeartRateViewController.swift; sourceTree = ""; };
76 | 1A2D10BB1C50FFFF0076F7E4 /* HeartRateViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeartRateViewControllerTests.swift; sourceTree = ""; };
77 | 1A6EC44A1C512DFF00A042EA /* BluetoothController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothController.swift; sourceTree = ""; };
78 | 1A8F45841C443382002B45B7 /* MockPeripheralTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockPeripheralTests.swift; sourceTree = ""; };
79 | 1A94609A1C5141DD00A0F04B /* BluetoothControllerState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothControllerState.swift; sourceTree = ""; };
80 | 1A94A8011C638F9F00250E7A /* HeartRateRecorderIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeartRateRecorderIntegrationTests.swift; sourceTree = ""; };
81 | 1AA255601C40246300A7DAE5 /* Beats.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Beats.app; sourceTree = BUILT_PRODUCTS_DIR; };
82 | 1AA255631C40246300A7DAE5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
83 | 1AA255681C40246300A7DAE5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
84 | 1AA2556D1C40246300A7DAE5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
85 | 1AA2556F1C40246300A7DAE5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
86 | 1AA255741C40246300A7DAE5 /* BeatsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BeatsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
87 | 1AA2557A1C40246300A7DAE5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
88 | 1AA2557F1C40246300A7DAE5 /* BeatsUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BeatsUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
89 | 1AA255851C40246300A7DAE5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
90 | 1AA255911C4024C400A7DAE5 /* HeartRateKitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeartRateKitTests.swift; sourceTree = ""; };
91 | 1AA255931C4024F900A7DAE5 /* HeartRateKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeartRateKit.swift; sourceTree = ""; };
92 | 1AA255951C40269D00A7DAE5 /* BluetoothControllerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothControllerProtocol.swift; sourceTree = ""; };
93 | 1AA255971C40270B00A7DAE5 /* MockBluetoothController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockBluetoothController.swift; sourceTree = ""; };
94 | 1AA2559B1C4029CC00A7DAE5 /* MockCentralManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockCentralManager.swift; sourceTree = ""; };
95 | 1AA2559D1C42E6E600A7DAE5 /* MockCentralManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockCentralManagerTests.swift; sourceTree = ""; };
96 | 1AA255A31C42F0C500A7DAE5 /* BluetoothControllerDelegateProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothControllerDelegateProtocol.swift; sourceTree = ""; };
97 | 1AA255A51C42F19700A7DAE5 /* MockCentralManagerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockCentralManagerDelegate.swift; sourceTree = ""; };
98 | 1AA5BD981C5A3B4200F0369B /* RecordingControlsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordingControlsViewController.swift; sourceTree = ""; };
99 | 1AA5BD9A1C5A403700F0369B /* RecordingControlsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordingControlsViewController.swift; sourceTree = ""; };
100 | 1AA5BD9D1C5A4A3100F0369B /* SessionRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionRecorder.swift; sourceTree = ""; };
101 | 1AA5BDA01C5A4C1100F0369B /* SessionRecorderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionRecorderTests.swift; sourceTree = ""; };
102 | 1AA5BDA21C5B8C0600F0369B /* SessionRecorderDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionRecorderDelegate.swift; sourceTree = ""; };
103 | 1AA5BDA41C5BA5ED00F0369B /* UIButtonWithAspectFit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIButtonWithAspectFit.swift; sourceTree = ""; };
104 | 1AB1B5D91C43CD3100AF8A6D /* MockPeripheral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockPeripheral.swift; sourceTree = ""; };
105 | 1AC766B51C612CAD00F5E1D4 /* Session.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = ""; };
106 | 1AC766B71C61360500F5E1D4 /* SessionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionTests.swift; sourceTree = ""; };
107 | 1AC766B91C613DA500F5E1D4 /* Record.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Record.swift; sourceTree = ""; };
108 | 1AC86D271C62666A00C92C70 /* RecordTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordTests.swift; sourceTree = ""; };
109 | 1AE2DD2F1C4936FD00FB395E /* UITest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITest.swift; sourceTree = ""; };
110 | 1AE2DD311C4BBE9200FB395E /* TestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = ""; };
111 | 1AE2DD331C4BDBD000FB395E /* MockBluetoothControllerConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockBluetoothControllerConnectionTests.swift; sourceTree = ""; };
112 | 1AE2DD351C4BDBFD00FB395E /* MockBluetoothControllerPeripheralTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockBluetoothControllerPeripheralTests.swift; sourceTree = ""; };
113 | 1AE2DD3B1C4C06B400FB395E /* MockPeripheralDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockPeripheralDelegate.swift; sourceTree = ""; };
114 | 1AE2DD3F1C4E57C500FB395E /* ArrayExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayExtension.swift; sourceTree = ""; };
115 | 1AE2DD421C4E646E00FB395E /* MockHeartRateMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockHeartRateMode.swift; sourceTree = ""; };
116 | /* End PBXFileReference section */
117 |
118 | /* Begin PBXFrameworksBuildPhase section */
119 | 1AA2555D1C40246300A7DAE5 /* Frameworks */ = {
120 | isa = PBXFrameworksBuildPhase;
121 | buildActionMask = 2147483647;
122 | files = (
123 | );
124 | runOnlyForDeploymentPostprocessing = 0;
125 | };
126 | 1AA255711C40246300A7DAE5 /* Frameworks */ = {
127 | isa = PBXFrameworksBuildPhase;
128 | buildActionMask = 2147483647;
129 | files = (
130 | );
131 | runOnlyForDeploymentPostprocessing = 0;
132 | };
133 | 1AA2557C1C40246300A7DAE5 /* Frameworks */ = {
134 | isa = PBXFrameworksBuildPhase;
135 | buildActionMask = 2147483647;
136 | files = (
137 | );
138 | runOnlyForDeploymentPostprocessing = 0;
139 | };
140 | /* End PBXFrameworksBuildPhase section */
141 |
142 | /* Begin PBXGroup section */
143 | 1A2D10AC1C4E95090076F7E4 /* HeartRateKit */ = {
144 | isa = PBXGroup;
145 | children = (
146 | 1AA255931C4024F900A7DAE5 /* HeartRateKit.swift */,
147 | 1A2D10B71C4FB8AC0076F7E4 /* HeartRateKitUIDelegate.swift */,
148 | 1A6EC44C1C512E0900A042EA /* BluetoothController */,
149 | 1AA2559B1C4029CC00A7DAE5 /* MockCentralManager.swift */,
150 | 1AA255A51C42F19700A7DAE5 /* MockCentralManagerDelegate.swift */,
151 | 1AB1B5D91C43CD3100AF8A6D /* MockPeripheral.swift */,
152 | 1A2D10AA1C4E691B0076F7E4 /* MockCharacteristic.swift */,
153 | 1AE2DD421C4E646E00FB395E /* MockHeartRateMode.swift */,
154 | 1AE2DD3B1C4C06B400FB395E /* MockPeripheralDelegate.swift */,
155 | );
156 | name = HeartRateKit;
157 | sourceTree = "";
158 | };
159 | 1A2D10AD1C4E95170076F7E4 /* HeartRateKit Tests */ = {
160 | isa = PBXGroup;
161 | children = (
162 | 1AA255911C4024C400A7DAE5 /* HeartRateKitTests.swift */,
163 | 1AE2DD331C4BDBD000FB395E /* MockBluetoothControllerConnectionTests.swift */,
164 | 1AE2DD351C4BDBFD00FB395E /* MockBluetoothControllerPeripheralTests.swift */,
165 | 1AA2559D1C42E6E600A7DAE5 /* MockCentralManagerTests.swift */,
166 | 1A8F45841C443382002B45B7 /* MockPeripheralTests.swift */,
167 | );
168 | name = "HeartRateKit Tests";
169 | sourceTree = "";
170 | };
171 | 1A2D10AE1C4EBA9B0076F7E4 /* ViewController Tests */ = {
172 | isa = PBXGroup;
173 | children = (
174 | 1A2D10AF1C4EBABC0076F7E4 /* ScanningViewControllerTests.swift */,
175 | 1A2D10BB1C50FFFF0076F7E4 /* HeartRateViewControllerTests.swift */,
176 | 1AA5BD9A1C5A403700F0369B /* RecordingControlsViewController.swift */,
177 | );
178 | name = "ViewController Tests";
179 | sourceTree = "";
180 | };
181 | 1A2D10B11C4EBAC60076F7E4 /* Helpers */ = {
182 | isa = PBXGroup;
183 | children = (
184 | 1AE2DD311C4BBE9200FB395E /* TestHelpers.swift */,
185 | );
186 | name = Helpers;
187 | sourceTree = "";
188 | };
189 | 1A2D10B61C4F990C0076F7E4 /* ViewControllers */ = {
190 | isa = PBXGroup;
191 | children = (
192 | 1A2D10B41C4F99070076F7E4 /* ScanningViewController.swift */,
193 | 1A2D10B91C5012050076F7E4 /* HeartRateViewController.swift */,
194 | 1AA5BD981C5A3B4200F0369B /* RecordingControlsViewController.swift */,
195 | );
196 | name = ViewControllers;
197 | sourceTree = "";
198 | };
199 | 1A2D10BF1C510F4C0076F7E4 /* Views */ = {
200 | isa = PBXGroup;
201 | children = (
202 | 1AA255671C40246300A7DAE5 /* Main.storyboard */,
203 | 1AA5BDA41C5BA5ED00F0369B /* UIButtonWithAspectFit.swift */,
204 | );
205 | name = Views;
206 | sourceTree = "";
207 | };
208 | 1A6EC44C1C512E0900A042EA /* BluetoothController */ = {
209 | isa = PBXGroup;
210 | children = (
211 | 1AA255951C40269D00A7DAE5 /* BluetoothControllerProtocol.swift */,
212 | 1A6EC44A1C512DFF00A042EA /* BluetoothController.swift */,
213 | 1AA255971C40270B00A7DAE5 /* MockBluetoothController.swift */,
214 | 1AA255A31C42F0C500A7DAE5 /* BluetoothControllerDelegateProtocol.swift */,
215 | 1A94609A1C5141DD00A0F04B /* BluetoothControllerState.swift */,
216 | );
217 | name = BluetoothController;
218 | sourceTree = "";
219 | };
220 | 1A94A8001C638F7200250E7A /* Integration Tests */ = {
221 | isa = PBXGroup;
222 | children = (
223 | 1A94A8011C638F9F00250E7A /* HeartRateRecorderIntegrationTests.swift */,
224 | );
225 | name = "Integration Tests";
226 | sourceTree = "";
227 | };
228 | 1AA255571C40246300A7DAE5 = {
229 | isa = PBXGroup;
230 | children = (
231 | 1AA255621C40246300A7DAE5 /* Beats */,
232 | 1AA255771C40246300A7DAE5 /* BeatsTests */,
233 | 1AA255821C40246300A7DAE5 /* BeatsUITests */,
234 | 1AA255611C40246300A7DAE5 /* Products */,
235 | );
236 | sourceTree = "";
237 | };
238 | 1AA255611C40246300A7DAE5 /* Products */ = {
239 | isa = PBXGroup;
240 | children = (
241 | 1AA255601C40246300A7DAE5 /* Beats.app */,
242 | 1AA255741C40246300A7DAE5 /* BeatsTests.xctest */,
243 | 1AA2557F1C40246300A7DAE5 /* BeatsUITests.xctest */,
244 | );
245 | name = Products;
246 | sourceTree = "";
247 | };
248 | 1AA255621C40246300A7DAE5 /* Beats */ = {
249 | isa = PBXGroup;
250 | children = (
251 | 1AA255631C40246300A7DAE5 /* AppDelegate.swift */,
252 | 1A2D10BF1C510F4C0076F7E4 /* Views */,
253 | 1A2D10B61C4F990C0076F7E4 /* ViewControllers */,
254 | 1A2D10AC1C4E95090076F7E4 /* HeartRateKit */,
255 | 1AA5BD9C1C5A4A1C00F0369B /* SessionRecorder */,
256 | 1AE2DD411C4E57C900FB395E /* Helpers */,
257 | 1AA2556C1C40246300A7DAE5 /* LaunchScreen.storyboard */,
258 | 1AA2556F1C40246300A7DAE5 /* Info.plist */,
259 | 1A2D10B21C4F92680076F7E4 /* Assets.xcassets */,
260 | );
261 | path = Beats;
262 | sourceTree = "";
263 | };
264 | 1AA255771C40246300A7DAE5 /* BeatsTests */ = {
265 | isa = PBXGroup;
266 | children = (
267 | 1AA2557A1C40246300A7DAE5 /* Info.plist */,
268 | 1A2D10AE1C4EBA9B0076F7E4 /* ViewController Tests */,
269 | 1A2D10AD1C4E95170076F7E4 /* HeartRateKit Tests */,
270 | 1AA5BD9F1C5A4BEA00F0369B /* SessionRecorderTests */,
271 | 1A94A8001C638F7200250E7A /* Integration Tests */,
272 | 1A2D10B11C4EBAC60076F7E4 /* Helpers */,
273 | );
274 | path = BeatsTests;
275 | sourceTree = "";
276 | };
277 | 1AA255821C40246300A7DAE5 /* BeatsUITests */ = {
278 | isa = PBXGroup;
279 | children = (
280 | 1AA255851C40246300A7DAE5 /* Info.plist */,
281 | 1AE2DD2F1C4936FD00FB395E /* UITest.swift */,
282 | );
283 | path = BeatsUITests;
284 | sourceTree = "";
285 | };
286 | 1AA5BD9C1C5A4A1C00F0369B /* SessionRecorder */ = {
287 | isa = PBXGroup;
288 | children = (
289 | 1AA5BD9D1C5A4A3100F0369B /* SessionRecorder.swift */,
290 | 1AC766B51C612CAD00F5E1D4 /* Session.swift */,
291 | 1AC766B91C613DA500F5E1D4 /* Record.swift */,
292 | 1AA5BDA21C5B8C0600F0369B /* SessionRecorderDelegate.swift */,
293 | );
294 | name = SessionRecorder;
295 | sourceTree = "";
296 | };
297 | 1AA5BD9F1C5A4BEA00F0369B /* SessionRecorderTests */ = {
298 | isa = PBXGroup;
299 | children = (
300 | 1AA5BDA01C5A4C1100F0369B /* SessionRecorderTests.swift */,
301 | 1AC766B71C61360500F5E1D4 /* SessionTests.swift */,
302 | 1AC86D271C62666A00C92C70 /* RecordTests.swift */,
303 | );
304 | name = SessionRecorderTests;
305 | sourceTree = "";
306 | };
307 | 1AE2DD411C4E57C900FB395E /* Helpers */ = {
308 | isa = PBXGroup;
309 | children = (
310 | 1AE2DD3F1C4E57C500FB395E /* ArrayExtension.swift */,
311 | );
312 | name = Helpers;
313 | sourceTree = "";
314 | };
315 | /* End PBXGroup section */
316 |
317 | /* Begin PBXNativeTarget section */
318 | 1AA2555F1C40246300A7DAE5 /* Beats */ = {
319 | isa = PBXNativeTarget;
320 | buildConfigurationList = 1AA255881C40246300A7DAE5 /* Build configuration list for PBXNativeTarget "Beats" */;
321 | buildPhases = (
322 | 1AA2555C1C40246300A7DAE5 /* Sources */,
323 | 1AA2555D1C40246300A7DAE5 /* Frameworks */,
324 | 1AA2555E1C40246300A7DAE5 /* Resources */,
325 | );
326 | buildRules = (
327 | );
328 | dependencies = (
329 | );
330 | name = Beats;
331 | productName = Beats;
332 | productReference = 1AA255601C40246300A7DAE5 /* Beats.app */;
333 | productType = "com.apple.product-type.application";
334 | };
335 | 1AA255731C40246300A7DAE5 /* BeatsTests */ = {
336 | isa = PBXNativeTarget;
337 | buildConfigurationList = 1AA2558B1C40246300A7DAE5 /* Build configuration list for PBXNativeTarget "BeatsTests" */;
338 | buildPhases = (
339 | 1AA255701C40246300A7DAE5 /* Sources */,
340 | 1AA255711C40246300A7DAE5 /* Frameworks */,
341 | 1AA255721C40246300A7DAE5 /* Resources */,
342 | );
343 | buildRules = (
344 | );
345 | dependencies = (
346 | 1AA255761C40246300A7DAE5 /* PBXTargetDependency */,
347 | );
348 | name = BeatsTests;
349 | productName = BeatsTests;
350 | productReference = 1AA255741C40246300A7DAE5 /* BeatsTests.xctest */;
351 | productType = "com.apple.product-type.bundle.unit-test";
352 | };
353 | 1AA2557E1C40246300A7DAE5 /* BeatsUITests */ = {
354 | isa = PBXNativeTarget;
355 | buildConfigurationList = 1AA2558E1C40246300A7DAE5 /* Build configuration list for PBXNativeTarget "BeatsUITests" */;
356 | buildPhases = (
357 | 1AA2557B1C40246300A7DAE5 /* Sources */,
358 | 1AA2557C1C40246300A7DAE5 /* Frameworks */,
359 | 1AA2557D1C40246300A7DAE5 /* Resources */,
360 | );
361 | buildRules = (
362 | );
363 | dependencies = (
364 | 1AA255811C40246300A7DAE5 /* PBXTargetDependency */,
365 | );
366 | name = BeatsUITests;
367 | productName = BeatsUITests;
368 | productReference = 1AA2557F1C40246300A7DAE5 /* BeatsUITests.xctest */;
369 | productType = "com.apple.product-type.bundle.ui-testing";
370 | };
371 | /* End PBXNativeTarget section */
372 |
373 | /* Begin PBXProject section */
374 | 1AA255581C40246300A7DAE5 /* Project object */ = {
375 | isa = PBXProject;
376 | attributes = {
377 | LastSwiftUpdateCheck = 0720;
378 | LastUpgradeCheck = 0720;
379 | ORGANIZATIONNAME = Yvette;
380 | TargetAttributes = {
381 | 1AA2555F1C40246300A7DAE5 = {
382 | CreatedOnToolsVersion = 7.2;
383 | DevelopmentTeam = EYC5Q3DCC6;
384 | };
385 | 1AA255731C40246300A7DAE5 = {
386 | CreatedOnToolsVersion = 7.2;
387 | TestTargetID = 1AA2555F1C40246300A7DAE5;
388 | };
389 | 1AA2557E1C40246300A7DAE5 = {
390 | CreatedOnToolsVersion = 7.2;
391 | TestTargetID = 1AA2555F1C40246300A7DAE5;
392 | };
393 | };
394 | };
395 | buildConfigurationList = 1AA2555B1C40246300A7DAE5 /* Build configuration list for PBXProject "Beats" */;
396 | compatibilityVersion = "Xcode 3.2";
397 | developmentRegion = English;
398 | hasScannedForEncodings = 0;
399 | knownRegions = (
400 | en,
401 | Base,
402 | );
403 | mainGroup = 1AA255571C40246300A7DAE5;
404 | productRefGroup = 1AA255611C40246300A7DAE5 /* Products */;
405 | projectDirPath = "";
406 | projectRoot = "";
407 | targets = (
408 | 1AA2555F1C40246300A7DAE5 /* Beats */,
409 | 1AA255731C40246300A7DAE5 /* BeatsTests */,
410 | 1AA2557E1C40246300A7DAE5 /* BeatsUITests */,
411 | );
412 | };
413 | /* End PBXProject section */
414 |
415 | /* Begin PBXResourcesBuildPhase section */
416 | 1AA2555E1C40246300A7DAE5 /* Resources */ = {
417 | isa = PBXResourcesBuildPhase;
418 | buildActionMask = 2147483647;
419 | files = (
420 | 1AA2556E1C40246300A7DAE5 /* LaunchScreen.storyboard in Resources */,
421 | 1A2D10B31C4F92680076F7E4 /* Assets.xcassets in Resources */,
422 | 1AA255691C40246300A7DAE5 /* Main.storyboard in Resources */,
423 | );
424 | runOnlyForDeploymentPostprocessing = 0;
425 | };
426 | 1AA255721C40246300A7DAE5 /* Resources */ = {
427 | isa = PBXResourcesBuildPhase;
428 | buildActionMask = 2147483647;
429 | files = (
430 | );
431 | runOnlyForDeploymentPostprocessing = 0;
432 | };
433 | 1AA2557D1C40246300A7DAE5 /* Resources */ = {
434 | isa = PBXResourcesBuildPhase;
435 | buildActionMask = 2147483647;
436 | files = (
437 | );
438 | runOnlyForDeploymentPostprocessing = 0;
439 | };
440 | /* End PBXResourcesBuildPhase section */
441 |
442 | /* Begin PBXSourcesBuildPhase section */
443 | 1AA2555C1C40246300A7DAE5 /* Sources */ = {
444 | isa = PBXSourcesBuildPhase;
445 | buildActionMask = 2147483647;
446 | files = (
447 | 1AA5BD991C5A3B4200F0369B /* RecordingControlsViewController.swift in Sources */,
448 | 1A2D10BA1C5012050076F7E4 /* HeartRateViewController.swift in Sources */,
449 | 1A2D10B81C4FB8AC0076F7E4 /* HeartRateKitUIDelegate.swift in Sources */,
450 | 1A2D10AB1C4E691B0076F7E4 /* MockCharacteristic.swift in Sources */,
451 | 1AA255A61C42F19700A7DAE5 /* MockCentralManagerDelegate.swift in Sources */,
452 | 1AA5BDA31C5B8C0600F0369B /* SessionRecorderDelegate.swift in Sources */,
453 | 1AE2DD431C4E646E00FB395E /* MockHeartRateMode.swift in Sources */,
454 | 1AA255961C40269D00A7DAE5 /* BluetoothControllerProtocol.swift in Sources */,
455 | 1AA255941C4024F900A7DAE5 /* HeartRateKit.swift in Sources */,
456 | 1AA255A41C42F0C500A7DAE5 /* BluetoothControllerDelegateProtocol.swift in Sources */,
457 | 1AB1B5DA1C43CD3100AF8A6D /* MockPeripheral.swift in Sources */,
458 | 1AE2DD3C1C4C06B400FB395E /* MockPeripheralDelegate.swift in Sources */,
459 | 1AC766BA1C613DA500F5E1D4 /* Record.swift in Sources */,
460 | 1A94609B1C5141DD00A0F04B /* BluetoothControllerState.swift in Sources */,
461 | 1AA5BDA51C5BA5ED00F0369B /* UIButtonWithAspectFit.swift in Sources */,
462 | 1AA255981C40270B00A7DAE5 /* MockBluetoothController.swift in Sources */,
463 | 1AA255641C40246300A7DAE5 /* AppDelegate.swift in Sources */,
464 | 1AA2559C1C4029CC00A7DAE5 /* MockCentralManager.swift in Sources */,
465 | 1AA5BD9E1C5A4A3100F0369B /* SessionRecorder.swift in Sources */,
466 | 1AC766B61C612CAD00F5E1D4 /* Session.swift in Sources */,
467 | 1AE2DD401C4E57C500FB395E /* ArrayExtension.swift in Sources */,
468 | 1A6EC44B1C512DFF00A042EA /* BluetoothController.swift in Sources */,
469 | 1A2D10B51C4F99070076F7E4 /* ScanningViewController.swift in Sources */,
470 | );
471 | runOnlyForDeploymentPostprocessing = 0;
472 | };
473 | 1AA255701C40246300A7DAE5 /* Sources */ = {
474 | isa = PBXSourcesBuildPhase;
475 | buildActionMask = 2147483647;
476 | files = (
477 | 1AA5BDA11C5A4C1100F0369B /* SessionRecorderTests.swift in Sources */,
478 | 1AA5BD9B1C5A403700F0369B /* RecordingControlsViewController.swift in Sources */,
479 | 1AC86D281C62666A00C92C70 /* RecordTests.swift in Sources */,
480 | 1AE2DD361C4BDBFD00FB395E /* MockBluetoothControllerPeripheralTests.swift in Sources */,
481 | 1AC766B81C61360500F5E1D4 /* SessionTests.swift in Sources */,
482 | 1AA2559E1C42E6E600A7DAE5 /* MockCentralManagerTests.swift in Sources */,
483 | 1A94A8021C638F9F00250E7A /* HeartRateRecorderIntegrationTests.swift in Sources */,
484 | 1AE2DD321C4BBE9200FB395E /* TestHelpers.swift in Sources */,
485 | 1A2D10BC1C50FFFF0076F7E4 /* HeartRateViewControllerTests.swift in Sources */,
486 | 1AE2DD341C4BDBD000FB395E /* MockBluetoothControllerConnectionTests.swift in Sources */,
487 | 1A8F45851C443382002B45B7 /* MockPeripheralTests.swift in Sources */,
488 | 1A2D10B01C4EBABC0076F7E4 /* ScanningViewControllerTests.swift in Sources */,
489 | 1AA255921C4024C400A7DAE5 /* HeartRateKitTests.swift in Sources */,
490 | );
491 | runOnlyForDeploymentPostprocessing = 0;
492 | };
493 | 1AA2557B1C40246300A7DAE5 /* Sources */ = {
494 | isa = PBXSourcesBuildPhase;
495 | buildActionMask = 2147483647;
496 | files = (
497 | 1AE2DD301C4936FD00FB395E /* UITest.swift in Sources */,
498 | );
499 | runOnlyForDeploymentPostprocessing = 0;
500 | };
501 | /* End PBXSourcesBuildPhase section */
502 |
503 | /* Begin PBXTargetDependency section */
504 | 1AA255761C40246300A7DAE5 /* PBXTargetDependency */ = {
505 | isa = PBXTargetDependency;
506 | target = 1AA2555F1C40246300A7DAE5 /* Beats */;
507 | targetProxy = 1AA255751C40246300A7DAE5 /* PBXContainerItemProxy */;
508 | };
509 | 1AA255811C40246300A7DAE5 /* PBXTargetDependency */ = {
510 | isa = PBXTargetDependency;
511 | target = 1AA2555F1C40246300A7DAE5 /* Beats */;
512 | targetProxy = 1AA255801C40246300A7DAE5 /* PBXContainerItemProxy */;
513 | };
514 | /* End PBXTargetDependency section */
515 |
516 | /* Begin PBXVariantGroup section */
517 | 1AA255671C40246300A7DAE5 /* Main.storyboard */ = {
518 | isa = PBXVariantGroup;
519 | children = (
520 | 1AA255681C40246300A7DAE5 /* Base */,
521 | );
522 | name = Main.storyboard;
523 | sourceTree = "";
524 | };
525 | 1AA2556C1C40246300A7DAE5 /* LaunchScreen.storyboard */ = {
526 | isa = PBXVariantGroup;
527 | children = (
528 | 1AA2556D1C40246300A7DAE5 /* Base */,
529 | );
530 | name = LaunchScreen.storyboard;
531 | sourceTree = "";
532 | };
533 | /* End PBXVariantGroup section */
534 |
535 | /* Begin XCBuildConfiguration section */
536 | 1AA255861C40246300A7DAE5 /* Debug */ = {
537 | isa = XCBuildConfiguration;
538 | buildSettings = {
539 | ALWAYS_SEARCH_USER_PATHS = NO;
540 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
541 | CLANG_CXX_LIBRARY = "libc++";
542 | CLANG_ENABLE_MODULES = YES;
543 | CLANG_ENABLE_OBJC_ARC = YES;
544 | CLANG_WARN_BOOL_CONVERSION = YES;
545 | CLANG_WARN_CONSTANT_CONVERSION = YES;
546 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
547 | CLANG_WARN_EMPTY_BODY = YES;
548 | CLANG_WARN_ENUM_CONVERSION = YES;
549 | CLANG_WARN_INT_CONVERSION = YES;
550 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
551 | CLANG_WARN_UNREACHABLE_CODE = YES;
552 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
553 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
554 | COPY_PHASE_STRIP = NO;
555 | DEBUG_INFORMATION_FORMAT = dwarf;
556 | ENABLE_STRICT_OBJC_MSGSEND = YES;
557 | ENABLE_TESTABILITY = YES;
558 | GCC_C_LANGUAGE_STANDARD = gnu99;
559 | GCC_DYNAMIC_NO_PIC = NO;
560 | GCC_NO_COMMON_BLOCKS = YES;
561 | GCC_OPTIMIZATION_LEVEL = 0;
562 | GCC_PREPROCESSOR_DEFINITIONS = (
563 | "DEBUG=1",
564 | "$(inherited)",
565 | );
566 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
567 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
568 | GCC_WARN_UNDECLARED_SELECTOR = YES;
569 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
570 | GCC_WARN_UNUSED_FUNCTION = YES;
571 | GCC_WARN_UNUSED_VARIABLE = YES;
572 | IPHONEOS_DEPLOYMENT_TARGET = 9.2;
573 | MTL_ENABLE_DEBUG_INFO = YES;
574 | ONLY_ACTIVE_ARCH = YES;
575 | SDKROOT = iphoneos;
576 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
577 | TARGETED_DEVICE_FAMILY = "1,2";
578 | };
579 | name = Debug;
580 | };
581 | 1AA255871C40246300A7DAE5 /* Release */ = {
582 | isa = XCBuildConfiguration;
583 | buildSettings = {
584 | ALWAYS_SEARCH_USER_PATHS = NO;
585 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
586 | CLANG_CXX_LIBRARY = "libc++";
587 | CLANG_ENABLE_MODULES = YES;
588 | CLANG_ENABLE_OBJC_ARC = YES;
589 | CLANG_WARN_BOOL_CONVERSION = YES;
590 | CLANG_WARN_CONSTANT_CONVERSION = YES;
591 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
592 | CLANG_WARN_EMPTY_BODY = YES;
593 | CLANG_WARN_ENUM_CONVERSION = YES;
594 | CLANG_WARN_INT_CONVERSION = YES;
595 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
596 | CLANG_WARN_UNREACHABLE_CODE = YES;
597 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
598 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
599 | COPY_PHASE_STRIP = NO;
600 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
601 | ENABLE_NS_ASSERTIONS = NO;
602 | ENABLE_STRICT_OBJC_MSGSEND = YES;
603 | GCC_C_LANGUAGE_STANDARD = gnu99;
604 | GCC_NO_COMMON_BLOCKS = YES;
605 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
606 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
607 | GCC_WARN_UNDECLARED_SELECTOR = YES;
608 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
609 | GCC_WARN_UNUSED_FUNCTION = YES;
610 | GCC_WARN_UNUSED_VARIABLE = YES;
611 | IPHONEOS_DEPLOYMENT_TARGET = 9.2;
612 | MTL_ENABLE_DEBUG_INFO = NO;
613 | SDKROOT = iphoneos;
614 | TARGETED_DEVICE_FAMILY = "1,2";
615 | VALIDATE_PRODUCT = YES;
616 | };
617 | name = Release;
618 | };
619 | 1AA255891C40246300A7DAE5 /* Debug */ = {
620 | isa = XCBuildConfiguration;
621 | buildSettings = {
622 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
623 | CODE_SIGN_IDENTITY = "iPhone Developer";
624 | INFOPLIST_FILE = Beats/Info.plist;
625 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
626 | PRODUCT_BUNDLE_IDENTIFIER = com.Yvette.Beats;
627 | PRODUCT_NAME = "$(TARGET_NAME)";
628 | };
629 | name = Debug;
630 | };
631 | 1AA2558A1C40246300A7DAE5 /* Release */ = {
632 | isa = XCBuildConfiguration;
633 | buildSettings = {
634 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
635 | CODE_SIGN_IDENTITY = "iPhone Developer";
636 | INFOPLIST_FILE = Beats/Info.plist;
637 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
638 | PRODUCT_BUNDLE_IDENTIFIER = com.Yvette.Beats;
639 | PRODUCT_NAME = "$(TARGET_NAME)";
640 | };
641 | name = Release;
642 | };
643 | 1AA2558C1C40246300A7DAE5 /* Debug */ = {
644 | isa = XCBuildConfiguration;
645 | buildSettings = {
646 | BUNDLE_LOADER = "$(TEST_HOST)";
647 | CLANG_ENABLE_MODULES = YES;
648 | INFOPLIST_FILE = BeatsTests/Info.plist;
649 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
650 | PRODUCT_BUNDLE_IDENTIFIER = com.Yvette.BeatsTests;
651 | PRODUCT_NAME = "$(TARGET_NAME)";
652 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
653 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Beats.app/Beats";
654 | };
655 | name = Debug;
656 | };
657 | 1AA2558D1C40246300A7DAE5 /* Release */ = {
658 | isa = XCBuildConfiguration;
659 | buildSettings = {
660 | BUNDLE_LOADER = "$(TEST_HOST)";
661 | CLANG_ENABLE_MODULES = YES;
662 | INFOPLIST_FILE = BeatsTests/Info.plist;
663 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
664 | PRODUCT_BUNDLE_IDENTIFIER = com.Yvette.BeatsTests;
665 | PRODUCT_NAME = "$(TARGET_NAME)";
666 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Beats.app/Beats";
667 | };
668 | name = Release;
669 | };
670 | 1AA2558F1C40246300A7DAE5 /* Debug */ = {
671 | isa = XCBuildConfiguration;
672 | buildSettings = {
673 | CLANG_ENABLE_MODULES = YES;
674 | INFOPLIST_FILE = BeatsUITests/Info.plist;
675 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
676 | PRODUCT_BUNDLE_IDENTIFIER = com.Yvette.BeatsUITests;
677 | PRODUCT_NAME = "$(TARGET_NAME)";
678 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
679 | TEST_TARGET_NAME = Beats;
680 | USES_XCTRUNNER = YES;
681 | };
682 | name = Debug;
683 | };
684 | 1AA255901C40246300A7DAE5 /* Release */ = {
685 | isa = XCBuildConfiguration;
686 | buildSettings = {
687 | CLANG_ENABLE_MODULES = YES;
688 | INFOPLIST_FILE = BeatsUITests/Info.plist;
689 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
690 | PRODUCT_BUNDLE_IDENTIFIER = com.Yvette.BeatsUITests;
691 | PRODUCT_NAME = "$(TARGET_NAME)";
692 | TEST_TARGET_NAME = Beats;
693 | USES_XCTRUNNER = YES;
694 | };
695 | name = Release;
696 | };
697 | /* End XCBuildConfiguration section */
698 |
699 | /* Begin XCConfigurationList section */
700 | 1AA2555B1C40246300A7DAE5 /* Build configuration list for PBXProject "Beats" */ = {
701 | isa = XCConfigurationList;
702 | buildConfigurations = (
703 | 1AA255861C40246300A7DAE5 /* Debug */,
704 | 1AA255871C40246300A7DAE5 /* Release */,
705 | );
706 | defaultConfigurationIsVisible = 0;
707 | defaultConfigurationName = Release;
708 | };
709 | 1AA255881C40246300A7DAE5 /* Build configuration list for PBXNativeTarget "Beats" */ = {
710 | isa = XCConfigurationList;
711 | buildConfigurations = (
712 | 1AA255891C40246300A7DAE5 /* Debug */,
713 | 1AA2558A1C40246300A7DAE5 /* Release */,
714 | );
715 | defaultConfigurationIsVisible = 0;
716 | defaultConfigurationName = Release;
717 | };
718 | 1AA2558B1C40246300A7DAE5 /* Build configuration list for PBXNativeTarget "BeatsTests" */ = {
719 | isa = XCConfigurationList;
720 | buildConfigurations = (
721 | 1AA2558C1C40246300A7DAE5 /* Debug */,
722 | 1AA2558D1C40246300A7DAE5 /* Release */,
723 | );
724 | defaultConfigurationIsVisible = 0;
725 | defaultConfigurationName = Release;
726 | };
727 | 1AA2558E1C40246300A7DAE5 /* Build configuration list for PBXNativeTarget "BeatsUITests" */ = {
728 | isa = XCConfigurationList;
729 | buildConfigurations = (
730 | 1AA2558F1C40246300A7DAE5 /* Debug */,
731 | 1AA255901C40246300A7DAE5 /* Release */,
732 | );
733 | defaultConfigurationIsVisible = 0;
734 | defaultConfigurationName = Release;
735 | };
736 | /* End XCConfigurationList section */
737 | };
738 | rootObject = 1AA255581C40246300A7DAE5 /* Project object */;
739 | }
740 |
--------------------------------------------------------------------------------