├── .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 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
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 |
--------------------------------------------------------------------------------