├── .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 | ![screenshot](./Design/screens.png) 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 | --------------------------------------------------------------------------------