├── .DS_Store ├── .gitignore ├── BLE.playground ├── Contents.swift ├── Resources │ └── TableView.xib ├── Sources │ ├── BluetoothScanner.swift │ ├── FindView.swift │ ├── Peripheral.swift │ ├── TableViewController.swift │ └── TableViewDataSource.swift └── contents.xcplayground ├── BLEPeripheral.playground ├── Contents.swift ├── Sources │ ├── CarsManager.swift │ ├── CharacteristicFactory.swift │ ├── MotionService.swift │ ├── MovmentCharacteristic.swift │ ├── PeripheralController.swift │ ├── PeripheralViewController.swift │ ├── RCCarModel.swift │ ├── ServiceController.swift │ └── ServiceFactory.swift └── contents.xcplayground ├── Ble-spec.md ├── Codable.playground ├── Contents.swift └── contents.xcplayground ├── MessagePassing.playground ├── Contents.swift └── contents.xcplayground ├── NotificationCenterExample.playground ├── Contents.swift └── contents.xcplayground └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gregiOS/Playgrounds/d3af735077d6dbdbf3228d0e775d33b5ae57ac33/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /BLE.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import Cocoa 4 | import PlaygroundSupport 5 | 6 | let tableViewController = TableViewController() 7 | let dataSource = tableViewController.dataSource 8 | 9 | PlaygroundPage.current.liveView = tableViewController.view 10 | 11 | let scanner = BluetoothScanner { scanner in 12 | scanner.startScanning { (peripheral) in 13 | print("Discovered peripheral: \(peripheral.tableViewData)") 14 | dataSource.update(peripheral: peripheral) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BLE.playground/Resources/TableView.xib: -------------------------------------------------------------------------------- 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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 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 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 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 | 160 | 161 | 165 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /BLE.playground/Sources/BluetoothScanner.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import CoreBluetooth 4 | 5 | open class BluetoothScanner: NSObject, CBCentralManagerDelegate { 6 | 7 | public typealias DidDiscoverPeripheralClosure = (PeripheralRepresntable) -> Void 8 | var centralManager: CBCentralManager! 9 | private var onScannerReady: ((BluetoothScanner) -> Void)? 10 | private var onDiscover: DidDiscoverPeripheralClosure? 11 | 12 | public init(onScannerReady: @escaping (BluetoothScanner) -> Void) { 13 | self.onScannerReady = onScannerReady 14 | super.init() 15 | centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main) 16 | } 17 | 18 | open func centralManagerDidUpdateState(_ central: CBCentralManager) { 19 | switch central.state { 20 | case .poweredOn: 21 | onScannerReady?(self) 22 | onScannerReady = nil 23 | case .poweredOff: 24 | central.stopScan() 25 | case .unsupported: fatalError("Unsupported BLE module") 26 | default: break 27 | } 28 | } 29 | 30 | open func startScanning(_ onDiscover: @escaping DidDiscoverPeripheralClosure) { 31 | self.onDiscover = onDiscover 32 | centralManager.scanForPeripherals(withServices: nil, options: nil) 33 | } 34 | 35 | // MARK: - CBCentralManagerDelegate 36 | 37 | public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { 38 | onDiscover?(peripheral.asPeripheral(advertisementData: advertisementData, rssi: RSSI.intValue)) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /BLE.playground/Sources/FindView.swift: -------------------------------------------------------------------------------- 1 | 2 | import Cocoa 3 | 4 | public func findView(of aClass: T.Type, in array: NSArray) -> T? { 5 | var foundView: T? 6 | for object in array { 7 | (object as? NSScrollView)?.subviews 8 | .forEach({ view in 9 | view.subviews.forEach({ 10 | if $0.isKind(of: aClass) { 11 | foundView = $0 as? T 12 | } 13 | }) 14 | }) 15 | } 16 | return foundView 17 | } 18 | -------------------------------------------------------------------------------- /BLE.playground/Sources/Peripheral.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import CoreBluetooth 4 | 5 | public protocol PeripheralRepresntable { 6 | var advertisedName: String? { get } 7 | var state: String { get } 8 | var rssi: String { get } 9 | var uuid: String { get } 10 | } 11 | 12 | extension PeripheralRepresntable { 13 | 14 | public var tableViewData: [String: String] { 15 | return [ 16 | "ID": uuid, 17 | "NAME": advertisedName ?? "unknown", 18 | "STATE": state, 19 | "RSSI": "\(rssi)" 20 | ] 21 | } 22 | 23 | } 24 | 25 | extension CBPeripheralState { 26 | var stringRepresentation: String { 27 | switch self { 28 | case .disconnected: return "disconnected" 29 | case .connected: return "connected" 30 | case .connecting: return "connecting" 31 | case .disconnecting: return "disconnecting" 32 | } 33 | } 34 | } 35 | 36 | extension CBPeripheral { 37 | public func asPeripheral(advertisementData: [String: Any],rssi: Int) -> PeripheralRepresntable { 38 | return Peripheral(self, advertisementData: advertisementData, rssi: rssi) 39 | } 40 | } 41 | 42 | struct Peripheral: PeripheralRepresntable { 43 | 44 | let advertisedName: String? 45 | var state: String { return peripheral.state.stringRepresentation } 46 | var uuid: String { return peripheral.identifier.uuidString } 47 | 48 | let rssi: String 49 | let peripheral: CBPeripheral 50 | 51 | init(_ peripheral: CBPeripheral, advertisementData: [String: Any], rssi: Int) { 52 | self.advertisedName = advertisementData[CBAdvertisementDataLocalNameKey] as? String ?? peripheral.name 53 | self.peripheral = peripheral 54 | self.rssi = "\(rssi)" 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /BLE.playground/Sources/TableViewController.swift: -------------------------------------------------------------------------------- 1 | 2 | import Cocoa 3 | 4 | public class TableViewController: NSViewController { 5 | 6 | public var dataSource = TableViewDataSource() 7 | var tableView: NSTableView! 8 | 9 | override public func loadView() { 10 | var topLevelObjects: NSArray? 11 | let nib = NSNib.init(nibNamed: .init("TableView"), bundle: nil)! 12 | guard nib.instantiate(withOwner: nil, topLevelObjects: &topLevelObjects) else { fatalError() } 13 | let views = (topLevelObjects as! Array).filter { $0 is NSView } 14 | view = views[0] as! NSView 15 | tableView = findView(of: NSTableView.self, in: topLevelObjects!)! 16 | dataSource.myTableView = tableView 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /BLE.playground/Sources/TableViewDataSource.swift: -------------------------------------------------------------------------------- 1 | // Created by Grzegorz Przybyła on 04/02/2018. 2 | // Copyright © 2018 Grzegorz Przybyła. All rights reserved. 3 | // 4 | 5 | import Cocoa 6 | 7 | public class TableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDelegate { 8 | 9 | public weak var myTableView: NSTableView? { 10 | didSet { 11 | myTableView.map { 12 | $0.dataSource = self 13 | $0.delegate = self 14 | } 15 | } 16 | } 17 | 18 | public var items: [PeripheralRepresntable] = [] { 19 | didSet { myTableView?.reloadData() } 20 | } 21 | 22 | public func numberOfRows(in tableView: NSTableView) -> Int { 23 | return items.count 24 | } 25 | 26 | public func update(peripheral: PeripheralRepresntable) { 27 | if let index = items.index(where: { $0.uuid == peripheral.uuid }) { 28 | items.insert(peripheral, at: index) 29 | return 30 | } 31 | items.append(peripheral) 32 | } 33 | 34 | public func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 35 | let item = items[row].tableViewData 36 | let columnIdentifier = tableColumn!.identifier 37 | let cell = tableView.makeView(withIdentifier: columnIdentifier, owner: self) as! NSTableCellView 38 | cell.textField!.stringValue = item[columnIdentifier.rawValue] ?? "" 39 | return cell 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /BLE.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /BLEPeripheral.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import Cocoa 4 | import CoreBluetooth 5 | import PlaygroundSupport 6 | import AppKit 7 | 8 | PlaygroundPage.current.needsIndefiniteExecution = true 9 | 10 | let viewController = PeripheralViewController() 11 | PlaygroundPage.current.liveView = viewController 12 | 13 | -------------------------------------------------------------------------------- /BLEPeripheral.playground/Sources/CarsManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol CarsManagerDelegate: class { 4 | func carsManager(_ carsManager: CarsManager, didUpdateCar carModel: RCCarModel) 5 | } 6 | 7 | public class CarsManager { 8 | 9 | public static let shared = CarsManager() 10 | 11 | public private(set) var cars: [String: RCCarModel] = [:] { 12 | didSet { 13 | cars.values.forEach({ delegate?.carsManager(self, didUpdateCar: $0) }) 14 | } 15 | } 16 | public weak var delegate: CarsManagerDelegate? 17 | 18 | public func addCar(_ car: RCCarModel) { 19 | cars[car.name] = car 20 | } 21 | 22 | public func changeMoveDirection(carName: String, direction: RCCarModel.MoveDirection) { 23 | cars[carName]?.moveDirection = direction 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BLEPeripheral.playground/Sources/CharacteristicFactory.swift: -------------------------------------------------------------------------------- 1 | import CoreBluetooth 2 | 3 | public class CharacteristicFactory { 4 | public static func makeMovmentCharacteristic() -> CBMutableCharacteristic { 5 | return CBMutableCharacteristic(type: CBUUID(string: "0x1a2b"), properties: [.read, .notify], value: nil, permissions: [.readable]) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /BLEPeripheral.playground/Sources/MotionService.swift: -------------------------------------------------------------------------------- 1 | import CoreBluetooth 2 | 3 | public class MotionService: ServiceController { 4 | public let service: CBMutableService 5 | let movmentCharacteristic = MovmentCharacteristic() 6 | 7 | public init(service: CBMutableService = ServiceFactory.makeMotionService()) { 8 | self.service = service 9 | service.characteristics = [movmentCharacteristic.characteristic] 10 | } 11 | 12 | public func handleReadRequest(_ request: CBATTRequest, peripheral: CBPeripheralManager) { 13 | guard request.characteristic.uuid == movmentCharacteristic.characteristic.uuid else { fatalError("Invalid request") } 14 | movmentCharacteristic.handleReadRequest(request, peripheral: peripheral) 15 | print("read value") 16 | } 17 | 18 | public func handleSubscribeToCharacteristic(characteristic: CBCharacteristic, on peripheral: CBPeripheralManager) { 19 | guard characteristic.uuid == movmentCharacteristic.characteristic.uuid else { fatalError("Invalid request") } 20 | movmentCharacteristic.handleSubscribeToCharacteristic(on: peripheral) 21 | print("\(#function) on \(peripheral)") 22 | } 23 | 24 | public func handleWriteRequest(_ request: CBATTRequest, peripheral: CBPeripheralManager) {} 25 | 26 | } 27 | -------------------------------------------------------------------------------- /BLEPeripheral.playground/Sources/MovmentCharacteristic.swift: -------------------------------------------------------------------------------- 1 | import CoreBluetooth 2 | 3 | protocol CharacteristicController { 4 | var characteristic: CBMutableCharacteristic { get } 5 | func handleReadRequest(_ request: CBATTRequest, peripheral: CBPeripheralManager) 6 | func handleWriteRequest(_ request: CBATTRequest, peripheral: CBPeripheralManager) 7 | func handleSubscribeToCharacteristic(on peripheral: CBPeripheralManager) 8 | } 9 | class MovmentCharacteristic: CarsManagerDelegate, CharacteristicController { 10 | 11 | let characteristic: CBMutableCharacteristic 12 | let carsManager: CarsManager 13 | private var peripheral: CBPeripheralManager? 14 | 15 | init(characteristic: CBMutableCharacteristic = CharacteristicFactory.makeMovmentCharacteristic(), 16 | carsManager: CarsManager = .shared) { 17 | self.carsManager = carsManager 18 | self.characteristic = characteristic 19 | carsManager.delegate = self 20 | } 21 | 22 | func handleReadRequest(_ request: CBATTRequest, peripheral: CBPeripheralManager) { 23 | guard let car = carsManager.cars.values.first else { return } 24 | request.value = car.moveDirection.bleData 25 | peripheral.respond(to: request, withResult: .success) 26 | } 27 | 28 | func handleSubscribeToCharacteristic(on peripheral: CBPeripheralManager) { 29 | self.peripheral = peripheral 30 | } 31 | 32 | func handleWriteRequest(_ request: CBATTRequest, peripheral: CBPeripheralManager) {} 33 | 34 | func carsManager(_ carsManager: CarsManager, didUpdateCar carModel: RCCarModel) { 35 | print("Updating value: \(carModel), on: \(peripheral)") 36 | peripheral?.updateValue(carModel.moveDirection.bleData, for: characteristic, onSubscribedCentrals: nil) 37 | } 38 | } 39 | 40 | extension RCCarModel.MoveDirection { 41 | var bleData: Data { 42 | switch self { 43 | case .forward: 44 | return Data([0x01]) 45 | case .backward: 46 | return Data([0x02]) 47 | case .left: 48 | return Data([0x03]) 49 | case .right: 50 | return Data([0x04]) 51 | case .none: 52 | return Data([0x04]) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /BLEPeripheral.playground/Sources/PeripheralController.swift: -------------------------------------------------------------------------------- 1 | import CoreBluetooth 2 | 3 | public class PeripheralController: NSObject, CBPeripheralManagerDelegate { 4 | 5 | enum Error: Swift.Error { 6 | case peripheralAlredyOn 7 | case peripheralAlreadyOff 8 | } 9 | 10 | private(set) var peripheral: CBPeripheralManager! 11 | let peripheralName: String 12 | private var serviceControllers: [ServiceController] = [] 13 | 14 | public init(peripheralName: String) { 15 | self.peripheralName = peripheralName 16 | super.init() 17 | } 18 | 19 | public func registerServiceController(_ serviceController: ServiceController) { 20 | serviceControllers.append(serviceController) 21 | } 22 | 23 | public func turnOn() throws { 24 | if peripheral != nil { throw Error.peripheralAlredyOn } 25 | peripheral = CBPeripheralManager(delegate: self, queue: .main) 26 | } 27 | 28 | public func turnOff() throws { 29 | if peripheral == nil || peripheral.state != .poweredOn { throw Error.peripheralAlreadyOff } 30 | serviceControllers = [] 31 | peripheral.stopAdvertising() 32 | peripheral = nil 33 | } 34 | 35 | private func startAdvertising() { 36 | print("Starting advertising") 37 | 38 | serviceControllers 39 | .map { $0.service } 40 | .forEach { peripheral.add($0) } 41 | 42 | let advertisementData: [String: Any] = [CBAdvertisementDataLocalNameKey: peripheralName, 43 | CBAdvertisementDataServiceUUIDsKey: serviceControllers.map({ $0.service.uuid })] 44 | peripheral.startAdvertising(advertisementData) 45 | } 46 | 47 | // MARK: - CBPeripheralManagerDelegate 48 | 49 | public func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { 50 | switch peripheral.state { 51 | case .poweredOn: 52 | print("Peirpheral is on") 53 | startAdvertising() 54 | case .poweredOff: 55 | print("Peripheral \(peripheral.description) is off") 56 | case .resetting: 57 | print("Peripheral \(peripheral.description) is resetting") 58 | case .unauthorized: 59 | print("Peripheral \(peripheral.description) is unauthorized") 60 | case .unsupported: 61 | print("Peripheral \(peripheral.description) is unsupported") 62 | case .unknown: 63 | print("Peripheral \(peripheral.description) state unknown") 64 | } 65 | } 66 | public func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) { 67 | print("\(#function)") 68 | let serviceUUID = request.characteristic.service.uuid 69 | serviceControllers 70 | .first(where: { $0.service.uuid == serviceUUID }) 71 | .map { $0.handleReadRequest(request, peripheral: peripheral) } 72 | } 73 | public func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) { 74 | print("\(#function)") 75 | } 76 | public func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) { 77 | let serviceUUID = characteristic.service.uuid 78 | serviceControllers 79 | .first(where: { $0.service.uuid == serviceUUID }) 80 | .map { $0.handleSubscribeToCharacteristic(characteristic: characteristic, on: peripheral) } 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /BLEPeripheral.playground/Sources/PeripheralViewController.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | 3 | public class PeripheralViewController: NSViewController { 4 | 5 | lazy var titleTextFiled: NSTextField = { 6 | let textFiled = NSTextField(frame: NSRect(x: 0, y: 598, width: 320, height: 35)) 7 | textFiled.font = NSFont.systemFont(ofSize: 25) 8 | textFiled.alignment = .center 9 | return textFiled 10 | }() 11 | 12 | lazy var peripheralNameTextFiled: NSTextField = { 13 | let textFiled = NSTextField(frame: NSRect(x: 0, y: 538, width: 320, height: 30)) 14 | textFiled.alignment = .left 15 | return textFiled 16 | }() 17 | 18 | lazy var isOnTextField = NSTextField(frame: NSRect(x: 0, y: 508, width: 160, height: 30)) 19 | 20 | lazy var isOnButton = NSButton(frame: NSRect(x: 160, y: 508, width: 160, height: 30)) 21 | 22 | lazy var goLeftButton: NSButton = { 23 | let button = NSButton(frame: NSRect(x: 60, y: 400, width: 72, height: 45)) 24 | button.title = "LEFT" 25 | button.alignment = .center 26 | return button 27 | }() 28 | 29 | lazy var goRightButton: NSButton = { 30 | let button = NSButton(frame: NSRect(x: 190, y: 400, width: 72, height: 45)) 31 | button.title = "RIGHT" 32 | button.alignment = .center 33 | return button 34 | }() 35 | 36 | lazy var goForwardButton: NSButton = { 37 | let button = NSButton(frame: NSRect(x: 120, y: 450, width: 85, height: 45)) 38 | button.title = "FORWARD" 39 | 40 | return button 41 | }() 42 | 43 | lazy var goBackwardButton: NSButton = { 44 | let button = NSButton(frame: NSRect(x: 120, y: 350, width: 85, height: 45)) 45 | button.title = "BACKWARD" 46 | button.alignment = .center 47 | return button 48 | }() 49 | 50 | var isPeripheralOn = false { 51 | didSet { 52 | isOnButton.title = isPeripheralOn ? "Turn Off" : "Turn On" 53 | if isPeripheralOn { 54 | peripheralController = PeripheralController(peripheralName: peripheralNameTextFiled.stringValue) 55 | peripheralController.registerServiceController(MotionService()) 56 | try? peripheralController.turnOn() 57 | carsManager.addCar(RCCarModel(name: peripheralController.peripheralName)) 58 | } else { 59 | try? peripheralController?.turnOff() 60 | peripheralController = nil 61 | } 62 | } 63 | } 64 | 65 | let carsManager = CarsManager.shared 66 | private(set) var peripheralController: PeripheralController! 67 | 68 | 69 | override public func loadView() { 70 | view = NSView(frame: NSRect(x: 0, y: 0, width: 320, height: 667)) 71 | view.layer = CALayer() 72 | view.layer?.backgroundColor = NSColor.white.cgColor 73 | view.addSubview(titleTextFiled) 74 | view.addSubview(peripheralNameTextFiled) 75 | view.addSubview(isOnTextField) 76 | view.addSubview(isOnButton) 77 | view.addSubview(goLeftButton) 78 | view.addSubview(goRightButton) 79 | view.addSubview(goForwardButton) 80 | view.addSubview(goBackwardButton) 81 | } 82 | 83 | override public func viewDidLoad() { 84 | super.viewDidLoad() 85 | titleTextFiled.stringValue = "Peripheral controller" 86 | peripheralNameTextFiled.stringValue = "MyRCCar" 87 | isOnTextField.stringValue = "IsOn" 88 | isOnButton.title = "Turn On" 89 | isOnButton.target = self 90 | isOnButton.action = #selector(didTapIsOnButton(_:)) 91 | [goLeftButton, goRightButton, goForwardButton, goBackwardButton].forEach { $0.target = self } 92 | goLeftButton.action = #selector(didTapGoLeftButton(_:)) 93 | goRightButton.action = #selector(didTapGoRightButton(_:)) 94 | goForwardButton.action = #selector(didTapGoForwardButton(_:)) 95 | goBackwardButton.action = #selector(didTapGoBackwardButton(_:)) 96 | } 97 | 98 | @objc func didTapIsOnButton(_ sender: NSButton) { 99 | isPeripheralOn = !isPeripheralOn 100 | } 101 | 102 | @objc func didTapGoLeftButton(_ sender: NSButton) { 103 | guard isPeripheralOn else { return } 104 | carsManager.changeMoveDirection(carName: peripheralController.peripheralName, direction: .left) 105 | } 106 | 107 | @objc func didTapGoRightButton(_ sender: NSButton) { 108 | guard isPeripheralOn else { return } 109 | carsManager.changeMoveDirection(carName: peripheralController.peripheralName, direction: .right) 110 | } 111 | 112 | @objc func didTapGoForwardButton(_ sender: NSButton) { 113 | guard isPeripheralOn else { return } 114 | carsManager.changeMoveDirection(carName: peripheralController.peripheralName, direction: .forward) 115 | } 116 | 117 | @objc func didTapGoBackwardButton(_ sender: NSButton) { 118 | guard isPeripheralOn else { return } 119 | carsManager.changeMoveDirection(carName: peripheralController.peripheralName, direction: .backward) 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /BLEPeripheral.playground/Sources/RCCarModel.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct RCCarModel { 4 | public enum MoveDirection { 5 | case forward, backward, left, right, none 6 | } 7 | public let name: String 8 | public var moveDirection: MoveDirection 9 | 10 | public init(name: String) { 11 | self.name = name 12 | self.moveDirection = .none 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /BLEPeripheral.playground/Sources/ServiceController.swift: -------------------------------------------------------------------------------- 1 | import CoreBluetooth 2 | 3 | public protocol ServiceController { 4 | var service: CBMutableService { get } 5 | func handleReadRequest(_ request: CBATTRequest, peripheral: CBPeripheralManager) 6 | func handleWriteRequest(_ request: CBATTRequest, peripheral: CBPeripheralManager) 7 | func handleSubscribeToCharacteristic(characteristic: CBCharacteristic, on peripheral: CBPeripheralManager) 8 | } 9 | -------------------------------------------------------------------------------- /BLEPeripheral.playground/Sources/ServiceFactory.swift: -------------------------------------------------------------------------------- 1 | import CoreBluetooth 2 | 3 | public class ServiceFactory { 4 | public static func makeMotionService() -> CBMutableService { 5 | return CBMutableService(type: CBUUID(string: "0x2FA2"), primary: true) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /BLEPeripheral.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Ble-spec.md: -------------------------------------------------------------------------------- 1 | BLE-Spec 2 | 3 | Here you can find the Bluetooth Low Energy specification for a Self-driving RC car that we would like to release on market at the begining of the next year. 4 | 5 | # Motion Service 6 | 7 | Motion Service UUID is 0x21A2 and it contain only one characteristic: **MoveDriection** 8 | 9 | # Move Direction 10 | Characteristic UUID = 0x211 11 | 12 | Move Direction characteristic can be read or subscribed. 13 | It returns on of the following values: 14 | 15 | 16 | | Byte | Value | Description | 17 | | ------------- |:-------------:| -----:| 18 | | 0 | 1 | Forward | 19 | | 0 | 2 | Backward | 20 | | 0 | 3 | Left | 21 | | 0 | 4 | Right | 22 | | 0 | 5 | None | 23 | -------------------------------------------------------------------------------- /Codable.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import UIKit 4 | 5 | var str = "Hello, playground" 6 | 7 | enum DisplayName { 8 | case firstName 9 | case secondName 10 | case fullName 11 | } 12 | 13 | extension DisplayName: Codable { 14 | 15 | enum Key: CodingKey { 16 | case rawValue 17 | } 18 | 19 | enum CodingError: Error { 20 | case unknownValue 21 | } 22 | 23 | init(from decoder: Decoder) throws { 24 | let container = try decoder.container(keyedBy: Key.self) 25 | let rawValue = try container.decode(Int.self, forKey: .rawValue) 26 | switch rawValue { 27 | case 0: 28 | self = .firstName 29 | case 1: 30 | self = .secondName 31 | case 2: 32 | self = .fullName 33 | default: 34 | throw CodingError.unknownValue 35 | } 36 | } 37 | 38 | func encode(to encoder: Encoder) throws { 39 | var container = encoder.container(keyedBy: Key.self) 40 | switch self { 41 | case .firstName: 42 | try container.encode(0, forKey: .rawValue) 43 | case .secondName: 44 | try container.encode(1, forKey: .rawValue) 45 | case .fullName: 46 | try container.encode(2, forKey: .rawValue) 47 | } 48 | } 49 | 50 | } 51 | 52 | let displayName = DisplayName.firstName 53 | 54 | let displayNameData = try? JSONEncoder().encode(displayName) 55 | 56 | let displayNameJson = String(data: displayNameData!, encoding: .utf8) 57 | 58 | 59 | // MARK: - LoginType 60 | 61 | enum LoginType { 62 | case name(userName: String) 63 | case email(String) 64 | } 65 | 66 | extension LoginType: Codable { 67 | 68 | enum Key: CodingKey { 69 | case rawValue 70 | case associatedValue 71 | } 72 | 73 | enum CodingError: Error { 74 | case unknownValue 75 | } 76 | 77 | init(from decoder: Decoder) throws { 78 | let container = try decoder.container(keyedBy: Key.self) 79 | let rawValue = try container.decode(Int.self, forKey: .rawValue) 80 | switch rawValue { 81 | case 0: 82 | let associtetedTypeValue = try container.decode(String.self, forKey: .associatedValue) 83 | self = .name(userName: associtetedTypeValue) 84 | case 1: 85 | let associtetedTypeValue = try container.decode(String.self, forKey: .associatedValue) 86 | self = .email(associtetedTypeValue) 87 | default: 88 | throw CodingError.unknownValue 89 | } 90 | } 91 | 92 | func encode(to encoder: Encoder) throws { 93 | var container = encoder.container(keyedBy: Key.self) 94 | switch self { 95 | case .name(let userName): 96 | try container.encode(0, forKey: .rawValue) 97 | try container.encode(userName, forKey: .associatedValue) 98 | case .email(let email): 99 | try container.encode(1, forKey: .rawValue) 100 | try container.encode(email, forKey: .associatedValue) 101 | } 102 | } 103 | 104 | } 105 | 106 | let loginType = LoginType.name(userName: "User") 107 | 108 | let data = try? JSONEncoder().encode(loginType) 109 | 110 | let stringData = String(data: data!, encoding: .utf8) 111 | 112 | // MARK: - AuthenticationType 113 | 114 | enum AuthenticationType { 115 | case password(String?) 116 | case twoFactorCode(String?) 117 | } 118 | 119 | extension AuthenticationType: Codable { 120 | 121 | enum Key: CodingKey { 122 | case rawValue 123 | case associatedValue 124 | } 125 | 126 | enum CodingError: Error { 127 | case unknownValue 128 | } 129 | 130 | init(from decoder: Decoder) throws { 131 | let container = try decoder.container(keyedBy: Key.self) 132 | let rawValue = try container.decode(Int.self, forKey: .rawValue) 133 | switch rawValue { 134 | case 0: 135 | let password = try container.decodeIfPresent(String.self, forKey: .associatedValue) 136 | self = .password(password) 137 | case 1: 138 | let code = try container.decodeIfPresent(String.self, forKey: .associatedValue) 139 | self = .twoFactorCode(code) 140 | default: 141 | throw CodingError.unknownValue 142 | } 143 | } 144 | 145 | func encode(to encoder: Encoder) throws { 146 | var container = encoder.container(keyedBy: Key.self) 147 | switch self { 148 | case .password(let password): 149 | try container.encode(0, forKey: .rawValue) 150 | try container.encode(password, forKey: .associatedValue) 151 | case .twoFactorCode(let code): 152 | try container.encode(1, forKey: .rawValue) 153 | try container.encode(code, forKey: .associatedValue) 154 | } 155 | } 156 | 157 | } 158 | 159 | let authentication = AuthenticationType.password(nil) 160 | let authenticationData = try? JSONEncoder().encode(authentication) 161 | let authenticationString = String(data: authenticationData!, encoding: .utf8) 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /Codable.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /MessagePassing.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | PlaygroundPage.current.needsIndefiniteExecution = true 4 | 5 | protocol BubbleViewDelegate: class { 6 | func userDidTap(into bubbleView: BubbleView) 7 | } 8 | class BubbleView: UIView { 9 | override init(frame: CGRect) { 10 | super.init(frame: frame) 11 | setup() 12 | } 13 | required init?(coder aDecoder: NSCoder) { 14 | super.init(coder: aDecoder) 15 | setup() 16 | } 17 | private var delegateQueue: DispatchQueue = .main 18 | private weak var delegate: BubbleViewDelegate? 19 | 20 | func setDelegate(_ delegate: BubbleViewDelegate?, queue: DispatchQueue? = nil) { 21 | assert(self.delegate == nil, "Delegate was already set.") 22 | self.delegate = delegate 23 | queue.map { delegateQueue = $0 } 24 | } 25 | 26 | private func setup() { 27 | self.isUserInteractionEnabled = true 28 | let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(BubbleView.didTapIntoButton)) 29 | self.addGestureRecognizer(tapGestureRecognizer) 30 | } 31 | 32 | @objc func didTapIntoButton(_ sender: UITapGestureRecognizer) { 33 | delegateQueue.async { [weak self] in 34 | guard let self = self else { return } 35 | self.delegate?.userDidTap(into: self) 36 | } 37 | } 38 | } 39 | 40 | class ContainerViewController: UIViewController { 41 | 42 | lazy var bubbleView: BubbleView = { 43 | let bubbleView = BubbleView(frame: CGRect(x: 80, y: 0, width: 160, height: 160)) 44 | bubbleView.backgroundColor = .blue 45 | bubbleView.layer.cornerRadius = 80 46 | bubbleView.setDelegate(self, queue: .main) 47 | return bubbleView 48 | }() 49 | override func loadView() { 50 | super.loadView() 51 | view.addSubview(bubbleView) 52 | } 53 | } 54 | extension ContainerViewController: BubbleViewDelegate { 55 | func userDidTap(into bubbleView: BubbleView) { 56 | let currentBounds = view.bounds 57 | UIView.animate(withDuration: 1.5) { 58 | var frame = bubbleView.frame 59 | frame.origin.y = currentBounds.height 60 | bubbleView.frame = frame 61 | } 62 | } 63 | } 64 | let viewController = ContainerViewController() 65 | viewController.view.frame = CGRect(x: 0, y: 0, width: 320, height: 640) 66 | viewController.view.backgroundColor = .white 67 | PlaygroundPage.current.liveView = viewController.view 68 | 69 | -------------------------------------------------------------------------------- /MessagePassing.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /NotificationCenterExample.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | 4 | PlaygroundPage.current.needsIndefiniteExecution = true 5 | extension Notification.Name { 6 | static let urlSessionTaskDidStart = Notification.Name("didStartURLSessionTask") 7 | static let urlSessionTaskDidComplete = Notification.Name("didStartURLSessionTask") 8 | } 9 | 10 | extension Notification { 11 | static func makeURLSessionTaskNotification(_ urlSessionTask: URLSessionTask?, 12 | urlSession: URLSession?, 13 | forName name: Notification.Name) -> Notification { 14 | guard let urlSessionTask = urlSessionTask else { fatalError("URLSessionTask was empty.") } 15 | return Notification(name: name, object: urlSession, userInfo: [URLSessionTask.urlSessionTaskKey: urlSessionTask]) 16 | } 17 | } 18 | 19 | extension URLSessionTask { 20 | static let urlSessionTaskKey = "URLSessionTask.urlSessionTaskKey" 21 | } 22 | 23 | class APIClient { 24 | enum APIClientError: Swift.Error { 25 | case invalidStatusCode 26 | } 27 | let urlSession = URLSession(configuration: .default) 28 | let notificationCenter = NotificationCenter.default 29 | 30 | func perform(_ urlRequest: URLRequest, 31 | completion: @escaping (Data?, URLResponse?, Error?) -> Void) { 32 | var urlSessionTask: URLSessionTask? 33 | urlSessionTask = 34 | urlSession 35 | .dataTask(with: urlRequest) { [weak self] (data, response, error) in 36 | completion(data, response, error) 37 | self?.notificationCenter 38 | .post(.makeURLSessionTaskNotification(urlSessionTask, 39 | urlSession: self?.urlSession, 40 | forName: .urlSessionTaskDidStart)) 41 | } 42 | urlSessionTask?.resume() 43 | notificationCenter 44 | .post(.makeURLSessionTaskNotification(urlSessionTask, 45 | urlSession: urlSession, 46 | forName: .urlSessionTaskDidStart)) 47 | } 48 | 49 | func preformLogin(using urlRequest: URLRequest, completionHandler: @escaping (Bool, Error?) -> Void) { 50 | perform(urlRequest) { (data, urlResponse, error) in 51 | if let error = error { 52 | completionHandler(false, error) 53 | return 54 | } 55 | if let urlResponse = urlResponse as? HTTPURLResponse, 56 | (200..<300).contains(urlResponse.statusCode) { 57 | completionHandler(true, nil) 58 | } else { 59 | completionHandler(false, APIClientError.invalidStatusCode) 60 | } 61 | } 62 | } 63 | } 64 | 65 | class URLRequestsObserver { 66 | let notificationCenter = NotificationCenter.default 67 | private var tokens: [Any] = [] 68 | init() { 69 | registerForNotifications() 70 | } 71 | deinit { 72 | tokens.forEach(notificationCenter.removeObserver) 73 | } 74 | private func registerForNotifications() { 75 | notificationCenter 76 | .addObserver(forName: .urlSessionTaskDidStart, 77 | object: nil, 78 | queue: nil) { [weak self] (notification) in 79 | self?.handleURLSessionTaskDidStart(notification) 80 | } 81 | notificationCenter 82 | .addObserver(forName: .urlSessionTaskDidComplete, 83 | object: nil, 84 | queue: nil) { [weak self] (notification) in 85 | self?.handleURLSessionTaskDidComplete(notification) 86 | } 87 | } 88 | private func handleURLSessionTaskDidStart(_ notification: Notification) { 89 | guard let urlSesisonTask = notification.userInfo?[URLSessionTask.urlSessionTaskKey] as? URLSessionTask else { return } 90 | print("URL session task did start: \(urlSesisonTask)") 91 | } 92 | private func handleURLSessionTaskDidComplete(_ notification: Notification) { 93 | guard let urlSesisonTask = notification.userInfo?[URLSessionTask.urlSessionTaskKey] as? URLSessionTask else { return } 94 | print("URL session task did complete: \(urlSesisonTask)") 95 | } 96 | } 97 | 98 | let apiClient = APIClient() 99 | apiClient.preformLogin(using: URLRequest(url: URL(string: "https://api.github.com/v3/users")!)) { (isLoggedIn, error) in 100 | switch (isLoggedIn, error) { 101 | case (true, _): 102 | print("user was logged") 103 | case (false, let error?): 104 | print("error occured while login user: \(error)") 105 | default: 106 | print("Unknown stuff happend") 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /NotificationCenterExample.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Playgrounds 2 | You can check all my post playground here. 3 | 4 | # Codable.playground 5 | Check my medium article here [Codable enuams at Medium.com](https://blog.untitledkingdom.com/codable-enums-in-swift-3ab3dacf30ce) 6 | 7 | # BLE.playground 8 | Simple project presenting how to run the CBCentralManager on the MacOS Playground. Display found peripherals in the NSTableView. 9 | 10 | Check my article here: [Swift playground: Bluetooth Low Energy](https://blog.untitledkingdom.com/swift-playground-bluetooth-low-energy-8fe15eb2e6df) 11 | --------------------------------------------------------------------------------