├── .gitignore
├── BlueSwift.podspec
├── Bluetooth.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── BlueSwift-Sample.xcscheme
│ └── BlueSwift-iOS.xcscheme
├── Bluetooth
├── AdvertisementViewController.swift
├── AppDelegate.swift
├── ConnectionViewController.swift
└── Supporting Files
│ ├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Base.lproj
│ └── LaunchScreen.storyboard
│ ├── Info.plist
│ └── Main.storyboard
├── CHANGELOG.md
├── Configurations
├── Common
│ ├── Common-Base.xcconfig
│ ├── Common-Development.xcconfig
│ └── Common-Release.xcconfig
├── Framework
│ └── Framework-Base.xcconfig
├── Sample
│ └── Sample-Base.xcconfig
├── Tests
│ └── Tests-Base.xcconfig
└── xcconfigs
│ ├── Common
│ ├── Carthage.xcconfig
│ ├── Common.xcconfig
│ └── Variables.xcconfig
│ ├── Configurations
│ ├── Debug.xcconfig
│ └── Release.xcconfig
│ ├── Documentation
│ ├── Best-Practices.md
│ ├── Carthage.md
│ ├── CocoaPods.md
│ ├── Code-Signing.md
│ ├── Images
│ │ ├── usage-assign-project-configurations.gif
│ │ ├── usage-build.gif
│ │ ├── usage-delete-build-settings.gif
│ │ └── usage-update-info-plist.gif
│ ├── Tutorial.md
│ └── Variables.md
│ ├── LICENSE.md
│ ├── Platforms
│ ├── iOS.xcconfig
│ ├── macOS.xcconfig
│ ├── tvOS.xcconfig
│ └── watchOS.xcconfig
│ ├── README.md
│ └── Targets
│ ├── Application.xcconfig
│ ├── Extension.xcconfig
│ ├── Framework.xcconfig
│ └── Tests.xcconfig
├── Framework
├── Source Files
│ ├── Advertisement
│ │ ├── AdvertisementService.swift
│ │ └── BluetoothAdvertisement.swift
│ ├── Command
│ │ └── Command.swift
│ ├── Connection
│ │ ├── BluetoothConnection.swift
│ │ └── ConnectionService.swift
│ ├── Extensions
│ │ ├── Array.swift
│ │ ├── CBCharacteristic.swift
│ │ ├── CBManager.swift
│ │ ├── CBUUID.swift
│ │ ├── Data.swift
│ │ ├── Int.swift
│ │ ├── Sequence.swift
│ │ └── String.swift
│ ├── Model
│ │ ├── AdvertisementData.swift
│ │ ├── BluetoothAuthorizationStatus.swift
│ │ ├── BluetoothError.swift
│ │ ├── Characteristic.swift
│ │ ├── Configuration.swift
│ │ ├── Peripheral
│ │ │ ├── AdvertisablePeripheral.swift
│ │ │ ├── ConnectablePeripheral.swift
│ │ │ └── Peripheral.swift
│ │ └── Service.swift
│ └── Utilities
│ │ ├── DispatchQueueProtocol.swift
│ │ └── SynchronizedArray.swift
└── Supporting Files
│ └── Info.plist
├── LICENSE.md
├── PULL_REQUEST_TEMPLATE.md
├── Package.swift
├── Readme.md
├── Unit Tests
├── CommandTests.swift
├── ConfigurationTests.swift
├── ExtensionTests.swift
├── Info.plist
├── Mocks
│ └── MockDispatchQueue.swift
├── SequenceExtensionTests.swift
└── SynchronizedArrayTests.swift
├── bitrise.yml
├── docs
├── _config.yml
├── advertisementData.md
├── bluetoothAdvertisement.md
├── bluetoothConnection.md
├── characteristic.md
├── command.md
├── configuration.md
├── index.md
├── peripheral.md
└── service.md
└── logo.png
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .localized
3 |
4 | xcuserdata
5 | profile
6 | build
7 | .build
8 | output
9 | DerivedData
10 | *.mode1v3
11 | *.mode2v3
12 | *.perspectivev3
13 | *.pbxuser
14 | *.xccheckout
15 | *.moved-aside
16 | *.hmap
17 | *.ipa
18 | *.o
19 | *.LinkFileList
20 | *.hmap
21 | *~.nib
22 | *.swp
23 | *.dat
24 | *.dep
25 | *.framework.zip
26 |
27 | .idea
28 | *.iml
29 |
30 | Pods
31 | Carthage
32 |
--------------------------------------------------------------------------------
/BlueSwift.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 |
3 | spec.name = 'BlueSwift'
4 | spec.version = '1.1.6'
5 | spec.summary = 'Easy and lightweight CoreBluetooth wrapper written in Swift'
6 | spec.homepage = 'https://github.com/netguru/BlueSwift'
7 |
8 | spec.license = { type: 'MIT', file: 'LICENSE.md' }
9 | spec.authors = { 'Jan Posz' => 'jan.posz@netguru.co' }
10 | spec.source = { git: 'https://github.com/netguru/BlueSwift.git', tag: spec.version.to_s }
11 |
12 | spec.source_files = 'Framework/Source Files/**/*.swift'
13 |
14 | spec.requires_arc = true
15 | spec.frameworks = 'Foundation', 'CoreBluetooth'
16 |
17 | spec.swift_version = '5.3'
18 | spec.ios.deployment_target = '11.0'
19 |
20 | end
21 |
--------------------------------------------------------------------------------
/Bluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Bluetooth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Bluetooth.xcodeproj/xcshareddata/xcschemes/BlueSwift-Sample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Bluetooth.xcodeproj/xcshareddata/xcschemes/BlueSwift-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
77 |
78 |
79 |
80 |
81 |
91 |
92 |
98 |
99 |
100 |
101 |
107 |
108 |
114 |
115 |
116 |
117 |
119 |
120 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/Bluetooth/AdvertisementViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import UIKit
7 | import BlueSwift
8 |
9 | class AdvertisementViewController: UIViewController {
10 |
11 | @IBOutlet weak var notifyTextField: UITextField!
12 | @IBOutlet weak var readTextField: UITextField!
13 | @IBOutlet weak var textView: UITextView!
14 |
15 | let advertisement = BluetoothAdvertisement.shared
16 | lazy var characteristic = try! Characteristic(uuid: "1104FD87-820F-438A-B757-7AC2C15C2D56")
17 | lazy var otherCharacteristic = try! Characteristic(uuid: "1204FD87-820F-438A-B757-7AC2C15C2D56")
18 | lazy var service = try! Service(uuid: "1004FD87-820F-438A-B757-7AC2C15C2D56", characteristics: [characteristic, otherCharacteristic])
19 | lazy var peripheral: Peripheral = {
20 | let configuration = try! Configuration(services: [service], advertisement: "1004FD87-820F-438A-B757-7AC2C15C2D56")
21 | return Peripheral(configuration: configuration, advertisementData: [.localName("Test"), .servicesUUIDs("1004FD87-820F-438A-B757-7AC2C15C2D56")])
22 | }()
23 |
24 | @IBAction func advertise() {
25 | advertisement.advertise(peripheral: peripheral) { _ in
26 | print("Error during adverisement")
27 | }
28 | handleRequests()
29 | }
30 |
31 | @IBAction func update() {
32 | let command = Command.utf8String(notifyTextField.text!)
33 | advertisement.update(command, characteristic: characteristic) { error in
34 | print("Notified on central.")
35 | }
36 | }
37 |
38 | func handleRequests() {
39 | advertisement.writeRequestCallback = { [weak self] characteristic, data in
40 | guard
41 | var text = self?.textView.text,
42 | let data = data,
43 | let encoded = String(data: data, encoding: .utf8)
44 | else {
45 | return
46 | }
47 | text = text + "\nWrote: \(encoded). Characteristic: \(characteristic.uuid)"
48 | self?.textView.text = text
49 | }
50 |
51 | advertisement.readRequestCallback = { [weak self] characteristic -> Data in
52 | guard
53 | var text = self?.textView.text,
54 | let notifyValue = self?.readTextField.text
55 | else {
56 | return Data()
57 | }
58 | text = text + "\nReceived read request. Characteristic: \(characteristic.uuid)"
59 | self?.textView.text = text
60 | return notifyValue.data(using: .utf8, allowLossyConversion: false)!
61 | }
62 | }
63 |
64 | override func viewWillDisappear(_ animated: Bool) {
65 | super.viewWillDisappear(animated)
66 | advertisement.stopAdvertising()
67 | }
68 |
69 | @IBAction func hideKeyboard() {
70 | view.endEditing(true)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Bluetooth/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import UIKit
7 |
8 | @UIApplicationMain
9 | class AppDelegate: UIResponder, UIApplicationDelegate {
10 |
11 | var window: UIWindow?
12 |
13 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
14 | window = UIWindow(frame: UIScreen.main.bounds)
15 | window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
16 | window?.makeKeyAndVisible()
17 | return true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Bluetooth/ConnectionViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import UIKit
7 | import BlueSwift
8 |
9 | class ConnectionViewController: UIViewController {
10 |
11 | @IBOutlet weak var notifyLabel: UILabel!
12 | @IBOutlet weak var readLabel: UILabel!
13 | @IBOutlet weak var textField: UITextField!
14 |
15 | private var loadingState: LoadingState = .connected {
16 | didSet {
17 | let activityIndicator: UIActivityIndicatorView
18 | if #available(iOS 13, *) {
19 | activityIndicator = UIActivityIndicatorView(style: .medium)
20 | } else {
21 | activityIndicator = UIActivityIndicatorView(style: .gray)
22 | }
23 | activityIndicator.startAnimating()
24 |
25 | switch self.loadingState {
26 | case .loading:
27 | navigationItem.rightBarButtonItem = UIBarButtonItem(customView: activityIndicator)
28 | title = "Connecting"
29 | case .connected:
30 | navigationItem.rightBarButtonItem = nil
31 | title = "Connected"
32 | case .error(_):
33 | navigationItem.rightBarButtonItem = nil
34 | title = "Connection error"
35 | }
36 | }
37 | }
38 |
39 | let connection = BluetoothConnection.shared
40 | lazy var characteristic = try! Characteristic(uuid: "1104FD87-820F-438A-B757-7AC2C15C2D56", shouldObserveNotification: true)
41 | lazy var otherCharacteristic = try! Characteristic(uuid: "1204FD87-820F-438A-B757-7AC2C15C2D56", shouldObserveNotification: true)
42 | lazy var service = try! Service(uuid: "1004FD87-820F-438A-B757-7AC2C15C2D56", characteristics: [characteristic, otherCharacteristic])
43 | lazy var peripheral: Peripheral = {
44 | let configuration = try! Configuration(services: [service], advertisement: "1004FD87-820F-438A-B757-7AC2C15C2D56")
45 | return Peripheral(configuration: configuration)
46 | }()
47 |
48 | @IBAction func connect() {
49 | loadingState = .loading
50 | connection.connect(peripheral) { [weak self] error in
51 | if let error = error {
52 | self?.loadingState = .error(error)
53 | self?.title = "Connection failure"
54 | return
55 | } else {
56 | self?.title = "Connected"
57 | self?.loadingState = .connected
58 | }
59 | }
60 | handleNotifications()
61 | }
62 |
63 | func handleNotifications() {
64 | characteristic.notifyHandler = { [weak self] data in
65 | guard
66 | let data = data,
67 | let encoded = String(data: data, encoding: .utf8)
68 | else {
69 | return
70 | }
71 | self?.notifyLabel.text = "Notify result: \(encoded)"
72 | }
73 | }
74 |
75 | @IBAction func write() {
76 | let command = Command.utf8String(textField.text!)
77 | peripheral.write(command: command, characteristic: otherCharacteristic) { error in
78 | print("Did write")
79 | }
80 | }
81 |
82 | @IBAction func read() {
83 | peripheral.read(characteristic, handler: { [weak self] data, error in
84 | guard
85 | let data = data,
86 | let encoded = String(data: data, encoding: .utf8)
87 | else {
88 | return
89 | }
90 | self?.readLabel.text = "Read result: \(encoded)"
91 | })
92 | }
93 |
94 | override func viewWillDisappear(_ animated: Bool) {
95 | super.viewWillDisappear(animated)
96 | connection.disconnect(peripheral)
97 | }
98 |
99 | @IBAction func hideKeyboard() {
100 | view.endEditing(true)
101 | }
102 | }
103 |
104 | internal extension ConnectionViewController {
105 |
106 | /// Enum representing current state of connecting to the peripheral.
107 | enum LoadingState {
108 | case loading
109 | case connected
110 | case error(Error)
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/Bluetooth/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Bluetooth/Supporting Files/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 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Bluetooth/Supporting Files/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
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 | $(PRODUCT_BUNDLE_VERSION_STRING)
19 | CFBundleVersion
20 | $(PRODUCT_BUNDLE_VERSION)
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Bluetooth/Supporting Files/Main.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 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
45 |
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 |
86 |
87 |
88 |
89 |
90 |
91 |
98 |
99 |
100 |
101 |
102 |
103 |
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 |
169 |
170 |
171 |
172 |
173 |
174 |
181 |
188 |
194 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 |
4 | ## [1.1.6] - 2023-10-27
5 |
6 | ### Changed
7 |
8 | - Fix Swift 5.9 (Xcode 15) build error caused by `Data(bytes:count:)` initializer.
9 |
10 | ## [1.1.5] - 2023-01-24
11 |
12 | ### Changed
13 |
14 | - Fix official cocoapods release
15 |
16 | ## [1.1.4] - 2023-01-20
17 |
18 | ### Changed
19 |
20 | - Synchronize access to array storing peripherals to increase thread safety.
21 |
22 |
23 | ## [1.1.3] - 2022-10-25
24 |
25 | ### Changed
26 |
27 | - Peripheral which was connected, but it was not on the list of peripherals that should be connected, is disconnected. This change allows to discover this peripheral again.
28 |
29 | ## [1.1.2] - 2022-09-06
30 |
31 | ### Added
32 |
33 | - added settable `centralManagerStateUpdateHandler` public property to `BluetoothConnection` to monitor updates to Central Manager state.
34 |
35 | ## [1.1.1] - 2022-04-14
36 |
37 | ### Added
38 |
39 | - `bluetoothAuthorizationStatus` public property was added to `BluetoothConnection` to determine current Bluetooth authorization status.
40 | - `requestBluetoothAuthorization()` public method was added to `BluetoothConnection` to requests User for authorization to use Bluetooth.
41 | - `BluetoothAuthorizationStatus` enum describing Bluetooth authorization status
42 |
43 | ### Changed
44 |
45 | - `ConnectionService`: centralManager (`CBCentralManager`) is instantiated lazily to postpone showing Bluetooth authorization popup until it is needed.
46 |
47 | ## [1.1.0] - 2022-04-13
48 |
49 | ### Changed
50 |
51 | - enabled Swift library evolution (`BUILD_LIBRARY_FOR_DISTRIBUTION = YES`) to allow creating XCFrameworks using BlueSwift. See ["Library Evolution in Swift"](https://www.swift.org/blog/library-evolution/) official swift blogpost.
52 | This change:
53 | - allows modules built with different compiler versions to be used together in one app.
54 | - allows developers of binary frameworks to make additive changes to the API of their framework while remaining binary compatible with previous versions.
55 |
56 | ## [1.0.6] - 2022-04-08
57 |
58 | ### Added
59 |
60 | - added public `peripheralConnectionCancelledHandler(_:)` setable property to `BluetoothConnection` class. It is called when disconnecting a peripheral using `disconnect(_:)` is completed
61 |
62 | ### Changed
63 |
64 | - refactored `.filter(_:).first` to `first(where:)` for optimisation
65 |
--------------------------------------------------------------------------------
/Configurations/Common/Common-Base.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | #include "../xcconfigs/Common/Common.xcconfig"
7 |
8 | _BUILD_VERSION = 1.1.6
9 | _BUILD_NUMBER = 1
10 |
11 | _DEPLOYMENT_TARGET_IOS = 11.0
12 | _COMPILER_SWIFT_VERSION = 5.3.0
13 |
--------------------------------------------------------------------------------
/Configurations/Common/Common-Development.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | #include "Common-Base.xcconfig"
7 | #include "../xcconfigs/Configurations/Debug.xcconfig"
8 |
--------------------------------------------------------------------------------
/Configurations/Common/Common-Release.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | #include "Common-Base.xcconfig"
7 | #include "../xcconfigs/Configurations/Release.xcconfig"
8 |
--------------------------------------------------------------------------------
/Configurations/Framework/Framework-Base.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | #include "../xcconfigs/Platforms/iOS.xcconfig"
7 | #include "../xcconfigs/Targets/Framework.xcconfig"
8 |
9 | _BUNDLE_NAME = BlueSwift
10 | _BUNDLE_IDENTIFIER = co.netguru.lib.blueswift
11 | _BUNDLE_INFOPLIST_PATH = $(PROJECT_DIR)/Framework/Supporting Files/Info.plist
12 |
13 | ENABLE_TESTABILITY = YES
14 |
15 | // Build library for distribution to enable Swift library evolution
16 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES
17 |
--------------------------------------------------------------------------------
/Configurations/Sample/Sample-Base.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | #include "../xcconfigs/Platforms/iOS.xcconfig"
7 | #include "../xcconfigs/Targets/Application.xcconfig"
8 |
9 | _BUNDLE_NAME = BlueSwiftSample
10 | _BUNDLE_IDENTIFIER = co.netguru.lib.blueswift.sample
11 | _BUNDLE_INFOPLIST_PATH = $(PROJECT_DIR)/Bluetooth/Supporting Files/Info.plist
12 |
13 | _CODESIGN_STYLE = Manual
14 | _CODESIGN_IDENTITY = iPhone Developer
15 |
--------------------------------------------------------------------------------
/Configurations/Tests/Tests-Base.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | #include "../xcconfigs/Platforms/iOS.xcconfig"
7 | #include "../xcconfigs/Targets/Tests.xcconfig"
8 |
9 | _BUNDLE_NAME = BlueSwiftTests
10 | _BUNDLE_IDENTIFIER = co.netguru.lib.blueswift.tests
11 | _BUNDLE_INFOPLIST_PATH = $(PROJECT_DIR)/Unit Tests/Info.plist
12 |
13 | TEST_HOST = $(BUILT_PRODUCTS_DIR)/BlueSwiftSample.app/BlueSwiftSample
14 | BUNDLE_LOADER = $(TEST_HOST)
15 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Common/Carthage.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Common/Carthage.xcconfig
3 | //
4 | // Copyright © 2017 Netguru Sp. z o.o. All rights reserved.
5 | // Licensed under MIT License.
6 | //
7 | // This file contains base build settings for targets using Carthage.
8 | //
9 |
10 | // Path to top-level Carthage directory.
11 | _CARTHAGE_PATH = $(PROJECT_DIR)/Carthage
12 |
13 | // Path to platform-specific Carthage/Build directory.
14 | _CARTHAGE_BUILD_PATH[sdk=iphone*] = $(_CARTHAGE_PATH)/Build/iOS
15 | _CARTHAGE_BUILD_PATH[sdk=macosx*] = $(_CARTHAGE_PATH)/Build/Mac
16 | _CARTHAGE_BUILD_PATH[sdk=appletv*] = $(_CARTHAGE_PATH)/Build/tvOS
17 | _CARTHAGE_BUILD_PATH[sdk=watch*] = $(_CARTHAGE_PATH)/Build/watchOS
18 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Common/Common.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Common/Common.xcconfig
3 | //
4 | // Copyright © 2017 Netguru Sp. z o.o. All rights reserved.
5 | // Licensed under MIT License.
6 | //
7 | // This file contains base build settings for all targets.
8 | //
9 |
10 | // Include default values of variables.
11 | #include "Variables.xcconfig"
12 |
13 | // MARK: Build options
14 |
15 | // The format of debugging symbols.
16 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym
17 |
18 | // MARK: Search paths
19 |
20 | // Disable legacy-compatible header searching.
21 | ALWAYS_SEARCH_USER_PATHS = NO
22 |
23 | // Additional framework search paths.
24 | FRAMEWORK_SEARCH_PATHS = $(inherited) $(_CARTHAGE_BUILD_PATH) $(_COMPILER_FRAMEWORK_SEARCH_PATHS)
25 |
26 | // Additional header search paths.
27 | HEADER_SEARCH_PATHS = $(inherited) $(_COMPILER_OBJC_HEADER_SEARCH_PATHS)
28 |
29 | // MARK: Linking
30 |
31 | // Other linker flags.
32 | OTHER_LDFLAGS = $(inherited) $(_COMPILER_OBJC_LINKER_FLAGS)
33 |
34 | // MARK: Packaging
35 |
36 | // Path to `Info.plist` file.
37 | INFOPLIST_FILE = $(_BUNDLE_INFOPLIST_PATH)
38 |
39 | // `CFBundleVersionString`.
40 | PRODUCT_BUNDLE_VERSION_STRING = $(_BUILD_VERSION)
41 |
42 | // `CFBundleVersion`.
43 | PRODUCT_BUNDLE_VERSION = $(_BUILD_NUMBER)
44 |
45 | // `CFBundleIdentifier`.
46 | PRODUCT_BUNDLE_IDENTIFIER = $(_BUNDLE_IDENTIFIER)
47 |
48 | // Name of the product.
49 | PRODUCT_NAME = $(_BUNDLE_NAME)
50 |
51 | // MARK: Signing
52 |
53 | // Code signing style.
54 | CODE_SIGN_STYLE = $(_CODESIGN_STYLE)
55 |
56 | // Development team to use for code signing.
57 | DEVELOPMENT_TEAM = $(_CODESIGN_DEVELOPMENT_TEAM)
58 |
59 | // Identity to use for code signing.
60 | CODE_SIGN_IDENTITY = $(_CODESIGN_IDENTITY)
61 |
62 | // Provisioning profile specifier for code signing.
63 | PROVISIONING_PROFILE_SPECIFIER = $(_CODESIGN_PROFILE_SPECIFIER)
64 |
65 | // Path to `.entitlements` file for code signing.
66 | CODE_SIGN_ENTITLEMENTS = $(_CODESIGN_ENTITLEMENTS_PATH)
67 |
68 | // MARK: LLVM compiler
69 |
70 | // Disable link-time optimizations.
71 | LLVM_LTO = NO
72 |
73 | // Compile `NSAssert` assertions.
74 | ENABLE_NS_ASSERTIONS = YES
75 |
76 | // Require `objc_msgSend` to be cast before invocation.
77 | ENABLE_STRICT_OBJC_MSGSEND = YES
78 |
79 | // Preprocessor definitions, mapped from `_ENVIRONMENT`.
80 | GCC_PREPROCESSOR_DEFINITIONS = $(_ENVIRONMENTS) $(inherited)
81 |
82 | // Use GNU++11 C++ language standard.
83 | CLANG_CXX_LANGUAGE_STANDARD = gnu++11
84 |
85 | // Use LLVM C++ standard library.
86 | CLANG_CXX_LIBRARY = libc++
87 |
88 | // Enable module imports and definitions.
89 | CLANG_ENABLE_MODULES = YES
90 |
91 | // Enable Objective-C ARC.
92 | CLANG_ENABLE_OBJC_ARC = YES
93 |
94 | // Warn about block capture autoreleasing.
95 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES
96 |
97 | // Warn about suspucious commas.
98 | CLANG_WARN_COMMA = YES
99 |
100 | // Warn about suspicious empty loop bodies.
101 | CLANG_WARN_EMPTY_BODY = YES
102 |
103 | // Warn when a function will recursively call itself on every code path.
104 | CLANG_WARN_INFINITE_RECURSION = YES
105 |
106 | // Warn about non-literal null conversion.
107 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES
108 |
109 | // Whether to warn on suspicious implicit conversions.
110 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES
111 |
112 | // Warn about strict prototypes.
113 | CLANG_WARN_STRICT_PROTOTYPES = YES
114 |
115 | // Warn about unreachable code.
116 | CLANG_WARN_UNREACHABLE_CODE = YES
117 |
118 | // Warn about range-based `for` loops in C++.
119 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES
120 |
121 | // Warning about suspicious uses of `std::move`.
122 | CLANG_WARN_SUSPICIOUS_MOVE = YES
123 |
124 | // Warn when overriding deprecated Objective-C methods.
125 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES
126 |
127 | // Warn about implicit Objective-C literal conversions.
128 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES
129 |
130 | // Warn about direct accesses to the Objective-C `isa` pointer instead of using
131 | // the Objective-C runtime API.
132 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR
133 |
134 | // Warn about classes that unintentionally do not subclass a root class.
135 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR
136 |
137 | // Warn duplicate method declarations within the same `@interface`.
138 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
139 |
140 | // Warn about implicit capture of `self` (e.g. direct ivar access).
141 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
142 |
143 | // Whether to run the static analyzer with every build.
144 | RUN_CLANG_STATIC_ANALYZER = YES
145 |
146 | // Warn for misused nullability attributes.
147 | CLANG_ANALYZER_NONNULL = YES
148 |
149 | // Warn when a non-localized string is passed to a user-interface method
150 | // expecting a localized string.
151 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES
152 |
153 | // Warn about suspicius conversions between `NSNumber` and `CFNumberRef`.
154 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE
155 |
156 | // Warn when a floating-point value is used as a loop counter.
157 | CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES
158 |
159 | // Warn when using `rand()` and `random()` instead of `arc4random()`.
160 | CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES
161 |
162 | // Warn when using `strcpy() and `strcat()`.
163 | CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES
164 |
165 | // Warn about incorrect uses of `nullable` values.
166 | CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION = YES
167 |
168 | // Function calls should not be position-dependent.
169 | GCC_DYNAMIC_NO_PIC = NO
170 |
171 | // Warn if the same variable is declared in two binaries that are linked
172 | // together.
173 | GCC_NO_COMMON_BLOCKS = YES
174 |
175 | // Use GNU99 C variant.
176 | GCC_C_LANGUAGE_STANDARD = gnu99
177 |
178 | // Whether to precompile the prefix header (if one is specified).
179 | GCC_PRECOMPILE_PREFIX_HEADER = YES
180 |
181 | // Treat warnings as errors (a.k.a. hard mode).
182 | GCC_TREAT_WARNINGS_AS_ERRORS = $(_COMPILER_HARD_MODE)
183 |
184 | // Warn about 64-bit values being implicitly shortened to 32 bits.
185 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES
186 |
187 | // Warn about fields missing from structure initializers (only if designated
188 | // initializers aren't used).
189 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES
190 |
191 | // Warn when the returned value does not match its return type.
192 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR
193 |
194 | // Warn about the use of four-character constants.
195 | GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES
196 |
197 | // Warn about an aggregate data type's initializer not being fully bracketed.
198 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES
199 |
200 | // Whether to warn about unsafe comparisons between values of different
201 | // signedness.
202 | GCC_WARN_SIGN_COMPARE = YES
203 |
204 | // Warn if a variable might be clobbered by a setjmp call or if an automatic
205 | // variable is used without prior initialization.
206 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE
207 |
208 | // Warn about static functions that are unused.
209 | GCC_WARN_UNUSED_FUNCTION = YES
210 |
211 | // Whether to warn about labels that are unused.
212 | GCC_WARN_UNUSED_LABEL = YES
213 |
214 | // Warn about variables that are never used.
215 | GCC_WARN_UNUSED_VARIABLE = YES
216 |
217 | // Warn if a `@selector` expression refers to an undeclared selector.
218 | GCC_WARN_UNDECLARED_SELECTOR = YES
219 |
220 | // MARK: Asset catalog compiler
221 |
222 | // Asset to use as app icon.
223 | ASSETCATALOG_COMPILER_APPICON_NAME = $(_ASSET_ICON)
224 |
225 | // Asset to use as launch image.
226 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = $(_ASSET_LAUNCHIMAGE)
227 |
228 | // MARK: Swift compiler
229 |
230 | // Swift version to be used.
231 | SWIFT_VERSION = $(_COMPILER_SWIFT_VERSION)
232 |
233 | // Treat warning as errors (a.k.a. hard mode).
234 | SWIFT_TREAT_WARNINGS_AS_ERRORS = $(_COMPILER_HARD_MODE)
235 |
236 | // Compilation conditions for Swift, mapped from `_ENVIRONMENTS`.
237 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(_ENVIRONMENTS) $(inherited)
238 |
239 | // Additional flags passed to Swift compiler.
240 | OTHER_SWIFT_FLAGS = $(inherited) $(_COMPILER_SWIFT_FLAGS)
241 |
242 | // Path to bridging header between Swift and Objective-C.
243 | SWIFT_OBJC_BRIDGING_HEADER = $(_COMPILER_SWIFT_BRIDGING_HEADER_PATH)
244 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Common/Variables.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Common/Variables.xcconfig
3 | //
4 | // Copyright © 2017 Netguru Sp. z o.o. All rights reserved.
5 | // Licensed under MIT License.
6 | //
7 | // This file contains variables that are mapped to build settings.
8 | //
9 |
10 | // MARK: Environment
11 |
12 | // The environments build setting, typically overridden in environment-specific
13 | // `xcconfig` files.
14 | _ENVIRONMENTS = ENV_DEFAULT
15 |
16 | // MARK: Versioning
17 |
18 | // The build semantic version, mapped later to `CFBundleVersionString`.
19 | _BUILD_VERSION =
20 |
21 | // The build number, mapped later to `CFBundleVersion`.
22 | _BUILD_NUMBER = $(_BUILD_VERSION)
23 |
24 | // MARK: Bundle
25 |
26 | // Bundle name of the product, used by Xcode to create an `.app` bundle.
27 | _BUNDLE_NAME = $(TARGET_NAME)
28 |
29 | // Bundle identifier of the product.
30 | _BUNDLE_IDENTIFIER =
31 |
32 | /// Path to `Info.plist` file of the product.
33 | _BUNDLE_INFOPLIST_PATH =
34 |
35 | // Whether the bundle is `@testable`.
36 | _BUNDLE_TESTABLE = YES
37 |
38 | // MARK: Deployment
39 |
40 | // Minimum iOS deployment target.
41 | _DEPLOYMENT_TARGET_IOS =
42 |
43 | // Targeted device families on iOS.
44 | _DEPLOYMENT_DEVICES_IOS = 1,2
45 |
46 | // Minimum macOS deployment target.
47 | _DEPLOYMENT_TARGET_MACOS =
48 |
49 | // Minimum tvOS deployment target.
50 | _DEPLOYMENT_TARGET_TVOS =
51 |
52 | // Targeted device families on tvOS.
53 | _DEPLOYMENT_DEVICES_TVOS = 3
54 |
55 | // Minimum watchOS deployment target.
56 | _DEPLOYMENT_TARGET_WATCHOS =
57 |
58 | // Targeted device families on watchOS.
59 | _DEPLOYMENT_DEVICES_WATCHOS = 4
60 |
61 | // MARK: Signing
62 |
63 | // Code signign style.
64 | _CODESIGN_STYLE = Automatic
65 |
66 | // Development team to be used with `_CODESIGN_PROFILE_SPECIFIER` to manually
67 | // code sign the product.
68 | _CODESIGN_DEVELOPMENT_TEAM =
69 |
70 | // The identity to be used with `_CODESIGN_PROFILE_SPECIFIER` to manually code
71 | // sign the product.
72 | _CODESIGN_IDENTITY =
73 |
74 | // The provisioning profile specifier to be used with `_CODESIGN_IDENTITY` to
75 | // manually code sign the product.
76 | _CODESIGN_PROFILE_SPECIFIER =
77 |
78 | // Path to `.entitlements` file of the product.
79 | _CODESIGN_ENTITLEMENTS_PATH =
80 |
81 | // MARK: Assets
82 |
83 | // Name of the icon asset to be used.
84 | _ASSET_ICON =
85 |
86 | // Name of the launch image asset to be used.
87 | _ASSET_LAUNCHIMAGE =
88 |
89 | // MARK: Compiler
90 |
91 | // Whether to enable hard mode (a.k.a. treat warnings as errors).
92 | _COMPILER_HARD_MODE = YES
93 |
94 | // The version of Swift to be used by compiler.
95 | _COMPILER_SWIFT_VERSION =
96 |
97 | // Additional flags for the Swift compiler.
98 | _COMPILER_SWIFT_FLAGS =
99 |
100 | // Path to Swift bridging header to be used by compiler.
101 | _COMPILER_SWIFT_BRIDGING_HEADER_PATH =
102 |
103 | // Additional flags for Objective-C linker.
104 | _COMPILER_OBJC_LINKER_FLAGS =
105 |
106 | // Additional search paths when looking for headers.
107 | _COMPILER_OBJC_HEADER_SEARCH_PATHS =
108 |
109 | // Additional search paths when looking for frameworks.
110 | _COMPILER_FRAMEWORK_SEARCH_PATHS =
111 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Configurations/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Configurations/Debug.xcconfig
3 | //
4 | // Copyright © 2017 Netguru Sp. z o.o. All rights reserved.
5 | // Licensed under MIT License.
6 | //
7 | // This file contains build settings specific to debug configuration.
8 | //
9 |
10 | // MARK: Environment
11 |
12 | // Set debug environments.
13 | _ENVIRONMENTS = ENV_DEBUG
14 |
15 | // MARK: Architecture
16 |
17 | // Build only the active architecture.
18 | ONLY_ACTIVE_ARCH = YES
19 |
20 | // MARK: Build options
21 |
22 | // Allow `@testable` imports.
23 | ENABLE_TESTABILITY = $(_BUNDLE_TESTABLE)
24 |
25 | // MARK: Deployment
26 |
27 | // Do not strip debugging symbols when copying resources.
28 | COPY_PHASE_STRIP = NO
29 |
30 | // Do not strip debugging symbols when copying the built product to its final
31 | // installation location.
32 | STRIP_INSTALLED_PRODUCT = NO
33 |
34 | // MARK: Signing
35 |
36 | // Disable Developer ID timestamping.
37 | OTHER_CODE_SIGN_FLAGS = --timestamp=none
38 |
39 | // MARK: LLVM compiler
40 |
41 | // Disable GCC optimization.
42 | GCC_OPTIMIZATION_LEVEL = 0
43 |
44 | // Catch errors in integer arithmetic.
45 | OTHER_CFLAGS = -ftrapv
46 |
47 | // MARK: Asset compiler
48 |
49 | // Optimize assets for time.
50 | ASSETCATALOG_COMPILER_OPTIMIZATION = time
51 |
52 | // MARK: Swift compiler
53 |
54 | // Disable optimizations for Swift.
55 | SWIFT_OPTIMIZATION_LEVEL = -Onone
56 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Configurations/Release.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Configurations/Release.xcconfig
3 | //
4 | // Copyright © 2017 Netguru Sp. z o.o. All rights reserved.
5 | // Licensed under MIT License.
6 | //
7 | // This file contains build settings specific to release configuration.
8 | //
9 |
10 | // MARK: Environment
11 |
12 | // Set release environments.
13 | _ENVIRONMENTS = ENV_RELEASE
14 |
15 | // MARK: Architecture
16 |
17 | // Build for all architectures.
18 | ONLY_ACTIVE_ARCH = NO
19 |
20 | // MARK: Build options
21 |
22 | // Disallow `@testable` imports.
23 | ENABLE_TESTABILITY = NO
24 |
25 | // MARK: Deployment
26 |
27 | // Strip debugging symbols when copying resources.
28 | COPY_PHASE_STRIP = YES
29 |
30 | // Strip debugging symbols when copying the built product to its final
31 | // installation location.
32 | STRIP_INSTALLED_PRODUCT = YES
33 |
34 | // MARK: LLVM compiler
35 |
36 | // Enable GCC optimization.
37 | GCC_OPTIMIZATION_LEVEL = s
38 |
39 | // MARK: Asset compiler
40 |
41 | // Optimize assets for space.
42 | ASSETCATALOG_COMPILER_OPTIMIZATION = space
43 |
44 | // MARK: Swift compiler
45 |
46 | // Enable whole-module-optimization for Swift.
47 | SWIFT_OPTIMIZATION_LEVEL = -Owholemodule
48 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Documentation/Best-Practices.md:
--------------------------------------------------------------------------------
1 | # xcconfigs – Best Practices
2 |
3 | Each project has best practices on how to do various things, and [netguru/xcconfigs](https://github.com/netguru/xcconfigs) is no exception.
4 |
5 | ---
6 |
7 | ### Prefer creating custom build setting variables instead of new xcconfig files
8 |
9 | In case you need more control over which build setting is used in which target and configuration, instead of creating multiple `.xcconfig` files with similar, but slightly various content, prefer to create custom variables and map those to the real build settings instead.
10 |
11 | For example, in case you need to use different Bundle Identifier for multiple build configurations, prefer the following setup:
12 |
13 | ```none
14 | // Application.xcconfig
15 |
16 | _BUNDLE_IDENTIFIER = com.example.app$(__BUNDLE_IDENTIFIER_SUFFIX)
17 | ```
18 |
19 | ```none
20 | // Development.xcconfig
21 |
22 | __BUNDLE_IDENTIFIER_SUFFIX = .development
23 | ```
24 |
25 | ```none
26 | // Staging.xcconfig
27 |
28 | __BUNDLE_IDENTIFIER_SUFFIX = .staging
29 | ```
30 |
31 | Instead of this setup:
32 |
33 | ```none
34 | // Application-Development.xcconfig
35 |
36 | _BUNDLE_IDENTIFIER = com.example.app.development
37 | ```
38 |
39 | ```none
40 | // Application-Staging.xcconfig
41 |
42 | _BUNDLE_IDENTIFIER = com.example.app.staging
43 | ```
44 |
45 | ```none
46 | // Application-Release.xcconfig
47 |
48 | _BUNDLE_IDENTIFIER = com.example.app
49 | ```
50 |
51 | _Rationale: Keeping `.xcconfig` files simple and using custom build setting variables makes your configuration more maintainable and easier to understand for newcomers._
52 |
53 | ---
54 |
55 | ### Prefix your custom build setting variables by double underscore
56 |
57 | If you use custom build setting variables, prefix them by double underscore `__`.
58 |
59 | For example, **prefer this name**:
60 |
61 | ```none
62 | __FOO =
63 | ```
64 |
65 | Instead of these names:
66 |
67 | ```none
68 | _BAR =
69 | BAZ =
70 | ```
71 |
72 | _Rationale: Build settings with no underscores are "real build settings" used by compiler and understood by Xcode. Build settings with one underscore are used by [netguru/xcconfigs](https://github.com/netguru/xcconfigs). By using double underscore, you minimize the risk of accidentally overriding build settings from any of aforementioned places._
73 |
74 | ---
75 |
76 | ### Use variables provided by [netguru/xcconfigs](https://github.com/netguru/xcconfigs) instead of real build settings
77 |
78 | In case you need to customize build settings, prefer to override [variables](Variables.md) provided by [netguru/xcconfigs](https://github.com/netguru/xcconfigs).
79 |
80 | Additionally, try to inherit defult build settings by putting `$(inherited)` as a value of build setting that support inheritance.
81 |
82 | In other words, **prefer this**:
83 |
84 | ```none
85 | _COMPILER_FRAMEWORK_SEARCH_PATHS = $(inherited) $(PROJECT_DIR)/Custom/Path/To/Frameworks
86 | ```
87 |
88 | Instead of this:
89 |
90 | ```none
91 | FRAMEWORK_SEARCH_PATHS = $(PROJECT_DIR)/Custom/Path/To/Frameworks
92 | ```
93 |
94 | _Rationale: [netguru/xcconfigs](https://github.com/netguru/xcconfigs) uses some reasonable default values for many build settings and some of them are even composed out of many variables._
95 |
96 | ---
97 |
98 | ### Don't fight Xcode in case of problems
99 |
100 | [netguru/xcconfigs](https://github.com/netguru/xcconfigs) are here to help you, not interfere with or disable your work.
101 |
102 | If Xcode throws strange errors during compilation (or runtime), you suspect it's caused by using this project and you don't know how to fix it, let Xcode win (by using build settings in `*.xcodeproj` file or getting rid of `*.xcconfig` files at all).
103 |
104 | In addition, in case of problems, you are encouraged to [open an issue](https://github.com/netguru/xcconfigs/issues/new) so that our maintainers can help you.
105 |
106 | _Rationale: You don't want to fight your IDE._
107 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Documentation/Carthage.md:
--------------------------------------------------------------------------------
1 | # xcconfigs - Carthage
2 |
3 | This document describes best practices of working with [Carthage](https://github.com/Carthage/Carthage) using [netguru/xcconfigs](https://github.com/netguru/xcconfigs).
4 |
5 | ---
6 |
7 | ### Step 1: Include Carthage.xcconfig file
8 |
9 | This example assumes that a base `Base.xcconfig` file exists that is imported (explicitly or implicitly) by all other `*.xcconfig` files.
10 |
11 | In your `Base.xcconfig`, include `Carthage.xcconfig` file:
12 |
13 | ```none
14 | // Base.xcconfig
15 |
16 | #import "path/to/xcconfigs/Common/Carthge.xcconfig"
17 | ```
18 |
19 | By importing this file, you now have access to `_CARTHAGE_BUILD_PATH` build setting variable that is automatically used in `FRAMEWORK_SEARCH_PATHS` and that you can use in `carthage copy-frameworks` build phase.
20 |
21 | Note that by default [netguru/xcconfigs](https://github.com/netguru/xcconfigs) assumes that `Carthage` directory is in `$(PROJECT_DIR)/Carthage`. If you need to customize that, override `_CARTHAGE_PATH` in your `Base.xcconfig`:
22 |
23 | ```none
24 | // Base.xcconfig
25 |
26 | #include "path/to/xcconfigs/Common/Carthge.xcconfig"
27 | _CARTHAGE_PATH = $(PROJECT_DIR)/Custom/Path/To/Carthage
28 | ```
29 |
30 | ### Step 2: Use \_CARTHAGE_BUILD_PATH in copy-frameworks build phase
31 |
32 | When providing framework paths as inputs to your `carthage copy-frameworks` build phase, instead of providing explicit paths, you can take advantage of aforementioned `_CARTHAGE_BUILD_PATH` build setting variable.
33 |
34 | As a result, your input file list will look like this, **no matter the platform**:
35 |
36 | ```none
37 | $(_CARTHAGE_BUILD_PATH)/Result.framework
38 | $(_CARTHAGE_BUILD_PATH)/ReactiveSwift.framework
39 | $(_CARTHAGE_BUILD_PATH)/ReactiveCocoa.framework
40 | ```
41 |
42 | Instead of like this:
43 |
44 | ```none
45 | Carthage/Build/iOS/Result.framework
46 | Carthage/Build/iOS/ReactiveSwift.framework
47 | Carthage/Build/iOS/ReactiveCocoa.framework
48 | ```
49 |
50 | This will allow you to copy-paste the list in case you have a multi-platform project with multiple targets depending on the same frameworks, thus making the whole setup more maintainable and less error-prone.
51 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Documentation/CocoaPods.md:
--------------------------------------------------------------------------------
1 | # xcconfigs - CocoaPods
2 |
3 | This document describes best practices of working with [CocoaPods](https://cocoapods.org) using [netguru/xcconfigs](https://github.com/netguru/xcconfigs).
4 |
5 | ---
6 |
7 | ### Step 1: Provide proper configuration for CocoaPods
8 |
9 | For this example, let's assume you want to create an iOS app target with unit tests and have three build configurations in your project – `Development`, `Staging` and `Release`.
10 |
11 | To be sure that CocoaPods takes this into consideration (and doesn't assume you use default `Debug` and `Release` ones), provide information about them in your `Podfile`:
12 |
13 | ```none
14 | project 'MyProject',
15 | 'Development' => :debug,
16 | 'Staging' => :release,
17 | 'Release' => :release
18 |
19 | target 'Application' do
20 | ...
21 | end
22 |
23 | target 'Tests' do
24 | ...
25 | end
26 | ```
27 |
28 | Based on that, CocoaPods will generate the following `*.xcconfig` hierarchy:
29 |
30 | ```none
31 | Pods/Target Support Files
32 | │
33 | ├ Pods-Application
34 | │ ├ Pods-Application.development.xcconfig
35 | │ ├ Pods-Application.staging.xcconfig
36 | │ ├ Pods-Application.release.xcconfig
37 | │ └ ...
38 | │
39 | ├ Pods-Tests
40 | │ ├ Pods-Tests.development.xcconfig
41 | │ ├ Pods-Tests.staging.xcconfig
42 | │ ├ Pods-Tests.release.xcconfig
43 | │ └ ...
44 | │
45 | └ ...
46 | ```
47 |
48 | Yes, all of those should be included in appropriate places in your `*.xcconfig` files...
49 |
50 | ### Step 2: Recreate your own \*.xcconfig files
51 |
52 | As `#include` statements cannot be conditional in `*.xcconfig` files and as CocoaPods will complain if you ignore to include them, you have to other option than to recreate the above configuration:
53 |
54 | ```none
55 | Development - Development.xcconfig
56 | ├ Application - Application-Development.xcconfig
57 | └ Tests - Tests-Development.xcconfig
58 |
59 | Staging - Staging.xcconfig
60 | ├ Application - Application-Staging.xcconfig
61 | └ Tests - Tests-Staging.xcconfig
62 |
63 | Release - Release.xcconfig
64 | ├ Application - Application-Release.xcconfig
65 | └ Tests - Tests-Release.xcconfig
66 | ```
67 |
68 | It's good to create additional `Common.xcconfig`, `Application-Common.xcconfig` and `Tests-Common.xcconfig` files so that settings can be shared between all the versions. The next step assumes you did that.
69 |
70 | ### Step 3: Include CocoaPods's \*.xcconfig files in your ones
71 |
72 | Now, add proper `#include`s in your `*.xcconfig` files:
73 |
74 | ```none
75 | // Application-Common.xcconfig
76 |
77 | #include "path/to/xcconfigs/Platforms/iOS.xcconfig"
78 | #include "path/to/xcconfigs/Targets/Application.xcconfig"
79 | ```
80 |
81 | ```none
82 | // Application-Development.xcconfig
83 |
84 | #include "Application-Common.xcconfig"
85 | #include "path/to/Pods/Target Support Files/Pods-Application/Pods-Application.development.xcconfig"
86 | ```
87 |
88 | ```none
89 | // Application-Staging.xcconfig
90 |
91 | #include "Application-Common.xcconfig"
92 | #include "path/to/Pods/Target Support Files/Pods-Application/Pods-Application.staging.xcconfig"
93 | ```
94 |
95 | ```none
96 | // Application-Release.xcconfig
97 |
98 | #include "Application-Common.xcconfig"
99 | #include "path/to/Pods/Target Support Files/Pods-Application/Pods-Application.release.xcconfig"
100 | ```
101 |
102 | ```none
103 | // Tests-Common.xcconfig
104 |
105 | #include "path/to/xcconfigs/Platforms/iOS.xcconfig"
106 | #include "path/to/xcconfigs/Targets/Tests.xcconfig"
107 | ```
108 |
109 | ```none
110 | // Tests-Development.xcconfig
111 |
112 | #include "Tests-Common.xcconfig"
113 | #include "path/to/Pods/Target Support Files/Pods-Application/Pods-Tests.development.xcconfig"
114 | ```
115 |
116 | ```none
117 | // Tests-Staging.xcconfig
118 |
119 | #include "Tests-Common.xcconfig"
120 | #include "path/to/Pods/Target Support Files/Pods-Application/Pods-Tests.staging.xcconfig"
121 | ```
122 |
123 | ```none
124 | // Tests-Release.xcconfig
125 |
126 | #include "Tests-Common.xcconfig"
127 | #include "path/to/Pods/Target Support Files/Pods-Application/Pods-Tests.release.xcconfig"
128 | ```
129 |
130 | ### Step 4: Build the project!
131 |
132 | Everything should be configured properly by now. Test the configuration by building the project. If it builds without errors, all is good!
133 |
134 | If not, sorry... :trollface: [Please open an issue](https://github.com/netguru/xcconfigs/issues/new).
135 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Documentation/Code-Signing.md:
--------------------------------------------------------------------------------
1 | # xcconfigs – Code Signing
2 |
3 | Large-scale apps, especially the ones crated by a team of numerous people and having more than two (debug/release) build configurations, need powerful and flexible way of specifying code signing settings per each target and configuration.
4 |
5 | In this document you will see a couple of methods to do flexible manual code signing using [netguru/xcconfigs](https://github.com/netguru/xcconfigs).
6 |
7 | ---
8 |
9 | ### Method 1: Using custom build setting variables
10 |
11 | The first method is to map `_CODESIGN_*` build settings to custom build setting variables declared in configuration `.xcconfig` files.
12 |
13 | The following example assumes that `.xcconfig` files are assigned as follows:
14 |
15 | ```none
16 | Development - Development.xcconfig
17 | ├ Application - Application.xcconfig
18 | └ Tests - Tests.xcconfig
19 |
20 | Staging - Staging.xcconfig
21 | ├ Application - Application.xcconfig
22 | └ Tests - Tests.xcconfig
23 |
24 | Release - Release.xcconfig
25 | ├ Application - Application.xcconfig
26 | └ Tests - Tests.xcconfig
27 | ```
28 |
29 | In `Application.xcconfig` and `Tests.xcconfig` map `_CODESIGN_*` build settings to custom build settings variables that we will use in the next step:
30 |
31 | ```none
32 | // Application.xcconfig
33 |
34 | _CODESIGN_STYLE = Manual
35 | _CODESIGN_DEVELOPMENT_TEAM = $(__APPLICATION_CODESIGN_DEVELOPMENT_TEAM)
36 | _CODESIGN_IDENTITY = $(__APPLICATION_CODESIGN_IDENTITY)
37 | _CODESIGN_PROFILE_SPECIFIER = $(__APPLICATION_CODESIGN_PROFILE_SPECIFIER)
38 | ```
39 |
40 | Then, in `Development.xcconfig`, `Staging.xcconfig` and `Release.xcconfig` set values of previously mentioned custom build setting variables:
41 |
42 | ```none
43 | // Staging.xcconfig
44 |
45 | __APPLICATION_CODESIGN_DEVELOPMENT_TEAM = A1B2C3D4E5F6
46 | __APPLICATION_CODESIGN_IDENTITY = John Doe (A1B2C3D4E5F6)
47 | __APPLICATION_CODESIGN_PROFILE_SPECIFIER = MyProject Staging
48 | ```
49 |
50 | ```none
51 | // Release.xcconfig
52 |
53 | __APPLICATION_CODESIGN_DEVELOPMENT_TEAM = F6E5D4C3B2A1
54 | __APPLICATION_CODESIGN_IDENTITY = Jane Roe (F6E5D4C3B2A1)
55 | __APPLICATION_CODESIGN_PROFILE_SPECIFIER = MyProject Release
56 | ```
57 |
58 | There are two things worth noting about this example.
59 |
60 | Firstly, notice that `Development.xcconfig` does not set `__APPLICATION_CODESIGN_*` custom build setting variables because you'd probably don't want your debug builds to be code signed.
61 |
62 | Secondly, notice that unit tests bundle target remains not code signed, as `Tests.xcconfig` does not use `__APPLICATION_CODESIGN_*` custom build setting variables.
63 |
64 | If you want your unit tests bundle to be code signed (or you have another target that needs to be code signed), you can use the above method to define more custom build setting variables accordingly (such as `__TESTS_CODESIGN_*` in `Tests.xcconfig`).
65 |
66 | ---
67 |
68 | ### Method 2: Using multiple xcconfig files
69 |
70 | The second method is to use multiple `.xcconfig` files, one per target per build configuration.
71 |
72 | The following example assumes that `.xcconfig` files are assigned as follows:
73 |
74 | ```none
75 | Development - Development.xcconfig
76 | ├ Application - Application-Development.xcconfig
77 | └ Tests - Tests-Development.xcconfig
78 |
79 | Staging - Staging.xcconfig
80 | ├ Application - Application-Staging.xcconfig
81 | └ Tests - Tests-Staging.xcconfig
82 |
83 | Release - Release.xcconfig
84 | ├ Application - Application-Release.xcconfig
85 | └ Tests - Tests-Release.xcconfig
86 | ```
87 |
88 | In such case, you can directly set `_CODESIGN_*` build settings in `Application-Staging.xcconfig` and `Application-Release.xcconfig` files:
89 |
90 | ```none
91 | // Application-Staging.xcconfig
92 |
93 | _CODESIGN_STYLE = Manual
94 | _CODESIGN_DEVELOPMENT_TEAM = A1B2C3D4E5F6
95 | _CODESIGN_IDENTITY = John Doe (A1B2C3D4E5F6)
96 | _CODESIGN_PROFILE_SPECIFIER = MyProject Staging
97 | ```
98 |
99 | ```none
100 | // Application-Release.xcconfig
101 |
102 | _CODESIGN_STYLE = Manual
103 | _CODESIGN_DEVELOPMENT_TEAM = F6E5D4C3B2A1
104 | _CODESIGN_IDENTITY = Jane Roe (F6E5D4C3B2A1)
105 | _CODESIGN_PROFILE_SPECIFIER = MyProject Release
106 | ```
107 |
108 | Like after the first method, `Application-Development.xcconfig` does not set `_CODESIGN_*` build settings and, as neither do `Tests-*.xcconfig` files, unit tests bundle remains not code signed.
109 |
110 | Using this method, as opposed to the first method, does get rid of custom build setting variables. However, it requires 9, instead of 5 `.xcconfig` files to be created. It is up to you and your team mates to decide which method suits you better.
111 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Documentation/Images/usage-assign-project-configurations.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/BlueSwift/dacfbd87b80ca2593ab2c402c551eea3c8a56821/Configurations/xcconfigs/Documentation/Images/usage-assign-project-configurations.gif
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Documentation/Images/usage-build.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/BlueSwift/dacfbd87b80ca2593ab2c402c551eea3c8a56821/Configurations/xcconfigs/Documentation/Images/usage-build.gif
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Documentation/Images/usage-delete-build-settings.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/BlueSwift/dacfbd87b80ca2593ab2c402c551eea3c8a56821/Configurations/xcconfigs/Documentation/Images/usage-delete-build-settings.gif
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Documentation/Images/usage-update-info-plist.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/BlueSwift/dacfbd87b80ca2593ab2c402c551eea3c8a56821/Configurations/xcconfigs/Documentation/Images/usage-update-info-plist.gif
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Documentation/Tutorial.md:
--------------------------------------------------------------------------------
1 | # xcconfigs – Tutorial
2 |
3 | This document contains a tutorial on how to set up a new project using [netguru/xcconfigs](https://github.com/netguru/xcconfigs).
4 |
5 | ---
6 |
7 | ### Step 1: Install [netguru/xcconfigs](https://github.com/netguru/xcconfigs)
8 |
9 | Start by installing [netguru/xcconfigs](https://github.com/netguru/xcconfigs). In this example we'll use Carthage.
10 |
11 | Add the following dependency to your `Cartfile`:
12 |
13 | ```none
14 | github "netguru/xcconfigs" {version}
15 | ```
16 |
17 | Then, while in the root directory of your project, update the dependencies by running:
18 |
19 | ```sh
20 | $ carthage update xcconfigs
21 | ```
22 |
23 | This will add [netguru/xcconfigs](https://github.com/netguru/xcconfigs) to `Carthage/Checkouts/xcconfigs` directory.
24 |
25 | ### Step 2: Remove build settings from \*.xcodeproj file
26 |
27 | Open your project and remove default build settings created by Xcode.
28 |
29 | To do this, select the project in Navigator panel, go to Build Settings, select all by pressing `⌘A` and then nuke everything by pressing `⌫`. Repeat this step for all targets.
30 |
31 | 
32 |
33 | ### Step 3: Create your custom \*.xcconfig files
34 |
35 | In order to use [netguru/xcconfigs](https://github.com/netguru/xcconfigs), you need to create your own `*.xcconfig` files and `#include` appropriate [netguru/xcconfigs](https://github.com/netguru/xcconfigs) files in them.
36 |
37 | In this example, we'll create configuration files for a sample iOS app and its test bundle and we'll use the following structure:
38 |
39 | | File | Purpose | Includes |
40 | |:--------------|:------------------------------------------------------------|:---------------------------------------------------------------|
41 | | `Base` | Contains base build settings used by all targets. | `xcconfigs/Common/Common` |
42 | | `Debug` | Contains base build settings used in Debug configuration | `Base`
`xcconfigs/Configurations/Debug` |
43 | | `Release` | Contains base build settings used in Release configuration. | `Base`
`xcconfigs/Configurations/Release` |
44 | | `Application` | Contains build settings used by Application target. | `xcconfigs/Platforms/iOS`
`xcconfigs/Targets/Application` |
45 | | `Tests` | Contains build settings used by Unit Tests target. | `xcconfigs/Platforms/iOS`
`xcconfigs/Targets/Tests` |
46 |
47 | After this step, you should have the following directory tree:
48 |
49 | ```none
50 | Root
51 | │
52 | ├ Carthage
53 | │ └ Checkouts
54 | │ └ xcconfigs
55 | │ └ ...
56 | │
57 | ├ Configuration
58 | │ ├ Base.xcconfig
59 | │ ├ Debug.xcconfig
60 | │ ├ Release.xcconfig
61 | │ ├ Application.xcconfig
62 | │ └ Tests.xcconfig
63 | │
64 | ├ YourProject.xcodeproj
65 | │ └ ...
66 | │
67 | └ ...
68 | ```
69 |
70 | Note that before you actually fill your own `*.xcconfig` files, the project **will not build**, as it's still missing the required build settings.
71 |
72 | ### Step 4: Fill your custom \*.xcconfig files
73 |
74 | Now it's time to add appropriate `#include`s and build settings in your custom `*.xcconfig` files.
75 |
76 | ```none
77 | // Base.xcconfig
78 |
79 | #include "../Carthage/Checkouts/xcconfigs/Common/Common.xcconfig"
80 |
81 | _BUILD_VERSION = 1.1
82 | _DEPLOYMENT_TARGET_IOS = 8.0
83 | ```
84 |
85 | ```none
86 | // Debug.xcconfig
87 |
88 | #include "Base.xcconfig"
89 | #include "../Carthage/Checkouts/xcconfigs/Configurations/Debug.xcconfig"
90 | ```
91 |
92 | ```none
93 | // Release.xcconfig
94 |
95 | #include "Base.xcconfig"
96 | #include "../Carthage/Checkouts/xcconfigs/Configurations/Release.xcconfig"
97 | ```
98 |
99 | ```none
100 | // Application.xcconfig
101 |
102 | #include "../Carthage/Checkouts/xcconfigs/Platforms/iOS.xcconfig"
103 | #include "../Carthage/Checkouts/xcconfigs/Targets/Application.xcconfig"
104 |
105 | _BUNDLE_IDENTIFIER = com.example.foo
106 | _BUNDLE_INFOPLIST_PATH = $(PROJECT_DIR)/Path/To/Application-Info.plist
107 | ```
108 |
109 | ```none
110 | // Tests.xcconfig
111 |
112 | #include "../Carthage/Checkouts/xcconfigs/Platforms/iOS.xcconfig"
113 | #include "../Carthage/Checkouts/xcconfigs/Targets/Tests.xcconfig"
114 |
115 | _BUNDLE_IDENTIFIER = com.example.foo.tests
116 | _BUNDLE_INFOPLIST_PATH = $(PROJECT_DIR)/Path/To/Tests-Info.plist
117 | ```
118 |
119 | For more information on build setting variables, see [Variables.md](Variables.md).
120 |
121 | ### Step 5: Update your Info.plist file
122 |
123 | Now, in order to manage your build version, build number, bundle name and bundle identifier in your `Info.plist` files, you need to update them accordingly.
124 |
125 | To do that, open both your app's and your unit tests bundle's `Info.plist` and change the following keys:
126 |
127 | | Key | New Value |
128 | |:------------------------------------------------------------------|:-----------------------------------|
129 | | `CFBundleName`
(Bundle name) | `$(PRODUCT_NAME)` |
130 | | `CFBundleIdentifier`
(Bundle identifier) | `$(PRODUCT_BUNDLE_IDENTIFIER)` |
131 | | `CFBundleVersion`
(Bundle version) | `$(PRODUCT_BUNDLE_VERSION)` |
132 | | `CFBundleShortVersionString`
(Bundle versions string, short) | `$(PRODUCT_BUNDLE_VERSION_STRING)` |
133 |
134 | After the change, the `Info.plist` should look like in the below screenshot:
135 |
136 | 
137 |
138 | ### Step 6: Assign configurations in \*.xcodeproj file
139 |
140 | This is the final step.
141 |
142 | Select the project file in Navigator panel, go to project's General tab and assign appropriate `*.xcconfig` files in the Configurations section:
143 |
144 | 
145 |
146 | If Xcode can't see your custom `*.xcconfig` files, make sure they are included in the project.
147 |
148 | ### Step 7: Build the project!
149 |
150 | Everything should be configured properly by now. Test the configuration by building the project. If it builds without errors, all is good!
151 |
152 | 
153 |
154 | If not, sorry... :trollface: [Please open an issue](https://github.com/netguru/xcconfigs/issues/new).
155 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Documentation/Variables.md:
--------------------------------------------------------------------------------
1 | # xcconfigs - Variables
2 |
3 | [netguru/xcconfigs](https://github.com/netguru/xcconfigs) takes advantage of build setting variables to allow more flexible project configuration in comparison to using raw build settings.
4 |
5 | This document describes all public variables that you can override in your own `.xcconfig` files.
6 |
7 | ---
8 |
9 | ### Environment
10 |
11 | #### `_ENVIRONMENTS`
12 |
13 | Environments to be used by compiler. As this variable is mapped to compilation conditions, you may use it to write conditionally compiled code.
14 |
15 | Assuming you have a follofing `*.xcconfig` file:
16 |
17 | ```none
18 | // Staging.xcconfig
19 |
20 | _ENVIRONMENTS = ENV_STAGING
21 | ```
22 |
23 | You can use this in both Swift and Objective-C code:
24 |
25 | ```swift
26 | // Swift
27 |
28 | #if ENV_STAGING
29 | ...
30 | #endif
31 | ```
32 |
33 | ```objc
34 | // Objective-C
35 |
36 | #if defined(ENV_STAGING)
37 | ...
38 | #endif
39 | ```
40 |
41 | You may have more than one environment set up.
42 |
43 | ---
44 |
45 | ### Versioning
46 |
47 | #### `_BUILD_VERSION`
48 |
49 | Semantic version of the product or the whole project.
50 |
51 | #### `_BUILD_NUMBER`
52 |
53 | Build number of the product or the whole project.
54 |
55 | This build setting is particularly useful on a CI service. For example, assuming you use [Travis CI](https://travis-ci.com), you can directly set `_BUILD_NUMBER=$TRAVIS_BUILD_NUMBER` build setting when invoking `xcodebuild`.
56 |
57 | ---
58 |
59 | ### Bundle
60 |
61 | #### `_BUNDLE_NAME`
62 |
63 | Name of the product bundle.
64 |
65 | This is different from Target Name, in that it is used as a name of the executable. This means it has more restrictions when it comes to allowed characters – for example, Framework targets might be called `MyFramework (iOS)`, but, as `_BUNDLE_NAME` doesn't allow spaces, it has to be changed to just `MyFramework` or `MyFramework_iOS`.
66 |
67 | #### `_BUNDLE_IDENTIFIER`
68 |
69 | Identifier of the product bundle.
70 |
71 | This variable is used as `CFBundleIdentifier` in `Info.plist`.
72 |
73 | #### `_BUNDLE_INFOPLIST_PATH`
74 |
75 | Path to `Info.plist` file of the product bundle.
76 |
77 | #### `_BUNDLE_TESTABLE`
78 |
79 | Whether the product bundle is `@testable`.
80 |
81 | ---
82 |
83 | ### Deployment
84 |
85 | #### `_DEPLOYMENT_TARGET_IOS`
86 |
87 | Minimum deployment target of the product on iOS.
88 |
89 | #### `_DEPLOYMENT_DEVICES_IOS`
90 |
91 | Targeted device families on iOS. `1` means iPhone and `2` means iPad.
92 |
93 | #### `_DEPLOYMENT_TARGET_MACOS`
94 |
95 | Minimum deployment target of the product on macOS.
96 |
97 | #### `_DEPLOYMENT_TARGET_TVOS`
98 |
99 | Minimum deployment target of the product on tvOS.
100 |
101 | #### `_DEPLOYMENT_DEVICES_TVOS`
102 |
103 | Targeted device families on tvOS. `3` means Apple TV and is the only valid value.
104 |
105 | #### `_DEPLOYMENT_TARGET_WATCHOS`
106 |
107 | Minimum deployment target of the product on watchOS.
108 |
109 | #### `_DEPLOYMENT_DEVICES_WATCHOS`
110 |
111 | Targeted device families on watchOS. `4` means Apple Watch and is the only valid value.
112 |
113 | ---
114 |
115 | ### Code signing
116 |
117 | #### `_CODESIGN_STYLE`
118 |
119 | Code signing style. Can be either `Automatic` or `Manual`, case-sensitive.
120 |
121 | #### `_CODESIGN_DEVELOPMENT_TEAM`
122 |
123 | Development team used along with `_CODESIGN_IDENTITY` and `_CODESIGN_PROFILE_SPECIFIER` to manually code sign the product.
124 |
125 | #### `_CODESIGN_IDENTITY`
126 |
127 | Identity used along with `_CODESIGN_DEVELOPMENT_TEAM` and `_CODESIGN_PROFILE_SPECIFIER` to manually code sign the product.
128 |
129 | #### `_CODESIGN_PROFILE_SPECIFIER`
130 |
131 | Provisioning profile specifier used along with `_CODESIGN_DEVELOPMENT_TEAM` and `_CODESIGN_IDENTITY` to manually code sign the product.
132 |
133 | #### `_CODESIGN_ENTITLEMENTS_PATH`
134 |
135 | Path to `*.entitlements` file containing sandboxed capabilities of the product.
136 |
137 | ---
138 |
139 | ### Assets
140 |
141 | #### `_ASSET_ICON`
142 |
143 | Name of an asset that will be used as product application's icon.
144 |
145 | #### `_ASSET_LAUNCHIMAGE`
146 |
147 | Name of an asset that will be used as product application's launch image.
148 |
149 | ---
150 |
151 | ### Compiler
152 |
153 | #### `_COMPILER_HARD_MODE`
154 |
155 | Whether to enable hard mode in Objective-C and Swift compilers. :trollface:
156 |
157 | Hard mode enables "Treat warnings as errors" for both Objective-C and Swift. It should be on by default, however, you could opt-out of it in case you're dealing with a legacy project with lots of low-severity warnings.
158 |
159 | #### `_COMPILER_SWIFT_VERSION`
160 |
161 | Swift language version used by the product.
162 |
163 | #### `_COMPILER_SWIFT_FLAGS`
164 |
165 | Additional flags passed to Swift compiler.
166 |
167 | Of of the most useful flags you can take advantage in debug configurations is `-Xfrontend -debug-time-function-bodies` which will print how much time Swift compiler spent on compiling particular function (or computed property). This is very, very useful for debugging long compile times.
168 |
169 | #### `_COMPILER_SWIFT_BRIDGING_HEADER_PATH`
170 |
171 | Path to Objective-C → Swift bridging header.
172 |
173 | #### `_COMPILER_FRAMEWORK_SEARCH_PATHS`
174 |
175 | Framework search paths of Objective-C and Swift compilers.
176 |
177 | #### `_COMPILER_OBJC_HEADER_SEARCH_PATHS`
178 |
179 | Header search paths of Objective-C compiler.
180 |
181 | #### `_COMPILER_OBJC_LINKER_FLAGS`
182 |
183 | Additional flags passed to Objective-C linker.
184 |
185 | You may use this variable to link your target to libraries, such as `-lxml2`.
186 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright © 2017 Netguru Sp. z o.o.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Platforms/iOS.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Platforms/iOS.xcconfig
3 | //
4 | // Copyright © 2017 Netguru Sp. z o.o. All rights reserved.
5 | // Licensed under MIT License.
6 | //
7 | // This file contains build settings specific to iOS platform.
8 | //
9 |
10 | // MARK: Architecture
11 |
12 | // SDK root of iOS.
13 | SDKROOT = iphoneos
14 |
15 | // Supported platforms for iOS.
16 | SUPPORTED_PLATFORMS = iphoneos iphonesimulator
17 |
18 | // MARK: Deployment
19 |
20 | // Deployment target for iOS.
21 | IPHONEOS_DEPLOYMENT_TARGET = $(_DEPLOYMENT_TARGET_IOS)
22 |
23 | // Supported device families for iOS.
24 | TARGETED_DEVICE_FAMILY = $(_DEPLOYMENT_DEVICES_IOS)
25 |
26 | // MARK: Linking
27 |
28 | // Where to find embedded frameworks for iOS.
29 | LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks
30 |
31 | // MARK: Search paths
32 |
33 | // Xcode needs this to find archived headers if `SKIP_INSTALL` is set.
34 | HEADER_SEARCH_PATHS = $(inherited) $(OBJROOT)/UninstalledProducts/include
35 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Platforms/macOS.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Platforms/macOS.xcconfig
3 | //
4 | // Copyright © 2017 Netguru Sp. z o.o. All rights reserved.
5 | // Licensed under MIT License.
6 | //
7 | // This file contains build settings specific to macOS platform.
8 | //
9 |
10 | // MARK: Architecture
11 |
12 | // SDK root of macOS.
13 | SDKROOT = macosx
14 |
15 | // Supported platforms for macOS.
16 | SUPPORTED_PLATFORMS = macosx
17 |
18 | // MARK: Deployment
19 |
20 | // Deployment target for macOS.
21 | MACOSX_DEPLOYMENT_TARGET = $(_DEPLOYMENT_TARGET_MACOS)
22 |
23 | // MARK: Compiler
24 |
25 | // Whether to combine multiple image resolutions into a TIFF.
26 | COMBINE_HIDPI_IMAGES = YES
27 |
28 | // MARK: Linker
29 |
30 | // Where to find embedded frameworks for macOS.
31 | LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/../Frameworks @loader_path/../Frameworks
32 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Platforms/tvOS.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Platforms/tvOS.xcconfig
3 | //
4 | // Copyright © 2017 Netguru Sp. z o.o. All rights reserved.
5 | // Licensed under MIT License.
6 | //
7 | // This file contains build settings specific to tvOS platform.
8 | //
9 |
10 | // MARK: Architecture
11 |
12 | // SDK root of tvOS.
13 | SDKROOT = appletvos
14 |
15 | // Supported platforms for tvOS.
16 | SUPPORTED_PLATFORMS = appletvos appletvsimulator
17 |
18 | // MARK: Deployment
19 |
20 | // Deployment target for tvOS.
21 | TVOS_DEPLOYMENT_TARGET = $(_DEPLOYMENT_TARGET_TVOS)
22 |
23 | // Supported device families for tvOS.
24 | TARGETED_DEVICE_FAMILY = $(_DEPLOYMENT_DEVICES_TVOS)
25 |
26 | // MARK: Linker
27 |
28 | // Where to find embedded frameworks for tvOS.
29 | LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks
30 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Platforms/watchOS.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Platforms/watchOS.xcconfig
3 | //
4 | // Copyright © 2017 Netguru Sp. z o.o. All rights reserved.
5 | // Licensed under MIT License.
6 | //
7 | // This file contains build settings specific to watchOS platform.
8 | //
9 |
10 | // MARK: Architecture
11 |
12 | // SDK root of tvOS.
13 | SDKROOT = watchos
14 |
15 | // Supported platforms for watchOS.
16 | SUPPORTED_PLATFORMS = watchos watchsimulator
17 |
18 | // MARK: Deployment
19 |
20 | // Deployment target for watchOS.
21 | WATCHOS_DEPLOYMENT_TARGET = $(_DEPLOYMENT_TARGET_WATCHOS)
22 |
23 | // Supported device families for watchOS.
24 | TARGETED_DEVICE_FAMILY = $(_DEPLOYMENT_DEVICES_WATCHOS)
25 |
26 | // MARK: Linker
27 |
28 | // Where to find embedded frameworks for watchOS.
29 | LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks
30 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/README.md:
--------------------------------------------------------------------------------
1 | # xcconfigs
2 |
3 | 
4 | [](https://github.com/netguru/xcconfigs/releases)
5 | [](LICENSE.md)
6 |
7 | This is a collection of common Xcode build configuration files.
8 |
9 | ## Requirements
10 |
11 | The latest stable version of `xcconfigs` is compatible with **Xcode 10.0**.
12 |
13 | ## Documentation
14 |
15 | Documentation, tutorials and how-to's are available in [Documentation](Documentation) folder.
16 |
17 | ## Installation
18 |
19 | ### Carthage
20 |
21 | The preferred way of installing `xcconfigs` is using [Carthage](https://github.com/Carthage/Carthage):
22 |
23 | ```none
24 | github "netguru/xcconfigs" {version}
25 | ```
26 |
27 | ### Submodule
28 |
29 | You may also install `xcconfigs` as a git submodule:
30 |
31 | ```bash
32 | git submodule add https://github.com/netguru/xcconfigs.git {path}
33 | git submodule update --init --recursive
34 | ```
35 |
36 | ## Roadmap
37 |
38 | #### Version 0.7
39 |
40 | The main focus of this release will be fixing issues encountered when integrating version 0.6.
41 |
42 | ## About
43 |
44 | This project is made and maintained with ♡ by [Netguru](https://netguru.co).
45 |
46 | ### License
47 |
48 | This project is licensed under **MIT License**. See [LICENSE.md](LICENSE.md) for more info.
49 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Targets/Application.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Targets/Application.xcconfig
3 | //
4 | // Copyright © 2017 Netguru Sp. z o.o. All rights reserved.
5 | // Licensed under MIT License.
6 | //
7 | // This file contains build settings specific to application targets.
8 | //
9 |
10 | // MARK: Packaging
11 |
12 | // Define LLVM module.
13 | DEFINES_MODULE = YES
14 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Targets/Extension.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Targets/Extension.xcconfig
3 | //
4 | // Copyright © 2017 Netguru Sp. z o.o. All rights reserved.
5 | // Licensed under MIT License.
6 | //
7 | // This file contains build settings specific to extension targets.
8 | //
9 |
10 | // Inherit from Application target.
11 | #include "Application.xcconfig"
12 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Targets/Framework.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Targets/Framework.xcconfig
3 | //
4 | // Copyright © 2017 Netguru Sp. z o.o. All rights reserved.
5 | // Licensed under MIT License.
6 | //
7 | // This file contains build settings specific to framework targets.
8 | //
9 |
10 | // MARK: Build options
11 |
12 | // Disallow use of APIs that are not available to app extensions and linking to
13 | // frameworks that have not been built with this setting enabled.
14 | APPLICATION_EXTENSION_API_ONLY = YES
15 |
16 | // MARK: Deployment
17 |
18 | // Install in run path.
19 | INSTALL_PATH = @rpath
20 |
21 | // Skip installation.
22 | SKIP_INSTALL = YES
23 |
24 | // MARK: Linking
25 |
26 | // Enable framework to be included from any location as long as the run path
27 | // includes it.
28 | LD_DYLIB_INSTALL_NAME = @rpath/$(PRODUCT_NAME).$(WRAPPER_EXTENSION)/$(PRODUCT_NAME)
29 |
30 | // MARK: Packaging
31 |
32 | // Define LLVM module.
33 | DEFINES_MODULE = YES
34 |
--------------------------------------------------------------------------------
/Configurations/xcconfigs/Targets/Tests.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Targets/Tests.xcconfig
3 | //
4 | // Copyright © 2017 Netguru Sp. z o.o. All rights reserved.
5 | // Licensed under MIT License.
6 | //
7 | // This file contains build settings specific to test bundle targets.
8 | //
9 |
10 | // Inherit from Application target.
11 | #include "Application.xcconfig"
12 |
--------------------------------------------------------------------------------
/Framework/Source Files/Advertisement/AdvertisementService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | import CoreBluetooth
8 |
9 | /// Class implementing peripheral manager delegate. Manages advertisement state.
10 | internal final class AdvertisementService: NSObject {
11 |
12 | /// Peripheral manager used for advertisement.
13 | /// - SeeAlso: `CBPeripheralManager`
14 | private lazy var peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
15 |
16 | /// Boolean value indicating whether device should advertise.
17 | /// Used in Bluetooth state change handler to prevent unwanted advertisement.
18 | private var shouldAdvertise = false
19 |
20 | /// Currently advertising peripheral.
21 | private var peripheral: Peripheral?
22 |
23 | /// After notify subscribtion, subscribed centrals are stored here.
24 | private var subsribedCentrals = [CBCentral]()
25 |
26 | /// Callback called after receiving read request.
27 | internal var readCallback: ((Characteristic) -> (Data))?
28 |
29 | /// Callback called after receiving write request.
30 | internal var writeCallback: ((Characteristic, Data?) -> ())?
31 |
32 | /// Callback called upon upcoming errors.
33 | private var errorHandler: ((AdvertisementError) -> ())?
34 |
35 | /// Starts advertising peripheral with given configuration of services and characteristics.
36 | internal func startAdvertising(_ peripheral: Peripheral, errorHandler: ((AdvertisementError) -> ())?) {
37 | self.peripheral = peripheral
38 | self.errorHandler = errorHandler
39 | peripheralManager.startAdvertising(peripheral.advertisementData?.combined())
40 | shouldAdvertise = true
41 | }
42 |
43 | /// Stops advertising peripheral.
44 | internal func stopAdvertising() {
45 | peripheralManager.stopAdvertising()
46 | peripheral?.configuration.services
47 | .compactMap { $0.advertisementService }
48 | .forEach { peripheralManager.remove($0) }
49 | shouldAdvertise = false
50 | }
51 |
52 | /// Updates a value on given characteristic.
53 | internal func updateValue(_ value: Data, characteristic: Characteristic, errorHandler: ((AdvertisementError) -> ())?) {
54 | guard let advertisementCharacteristic = characteristic.advertisementCharacteristic else {
55 | errorHandler?(.deviceNotAdvertising)
56 | return
57 | }
58 | peripheralManager.updateValue(value, for: advertisementCharacteristic, onSubscribedCentrals: subsribedCentrals)
59 | }
60 | }
61 |
62 | extension AdvertisementService: CBPeripheralManagerDelegate {
63 |
64 | /// - SeeAlso: `CBPeripheralManagerDelegate`
65 | func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
66 | do {
67 | try peripheral.validateState()
68 | if !peripheralManager.isAdvertising && shouldAdvertise {
69 | peripheralManager.startAdvertising(self.peripheral?.advertisementData?.combined())
70 | }
71 | } catch let error {
72 | guard let error = error as? BluetoothError else { return }
73 | errorHandler?(.bluetoothError(error))
74 | }
75 | }
76 |
77 | /// - SeeAlso: `CBPeripheralManagerDelegate`
78 | func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
79 | if let error = error {
80 | errorHandler?(.otherError(error))
81 | return
82 | }
83 | self.peripheral?.configuration.services.map({ $0.assignAdvertisementService() }).forEach(peripheralManager.add(_:))
84 | }
85 |
86 | /// - SeeAlso: `CBPeripheralManagerDelegate`
87 | func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
88 | if let error = error {
89 | errorHandler?(.otherError(error))
90 | }
91 | }
92 |
93 | /// - SeeAlso: `CBPeripheralManagerDelegate`
94 | func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {
95 | let rawCharacteristic = request.characteristic
96 | guard let characteristic = self.peripheral?.configuration.characteristic(matching: rawCharacteristic) else { return }
97 | guard let data = readCallback?(characteristic) else { return }
98 | request.value = data
99 | peripheral.respond(to: request, withResult: .success)
100 | }
101 |
102 | /// - SeeAlso: `CBPeripheralManagerDelegate`
103 | func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
104 | requests.forEach { request in
105 | let rawCharacteristic = request.characteristic
106 | guard let characteristic = self.peripheral?.configuration.characteristic(matching: rawCharacteristic) else { return }
107 | writeCallback?(characteristic, request.value)
108 | peripheral.respond(to: request, withResult: .success)
109 | }
110 | }
111 |
112 | /// - SeeAlso: `CBPeripheralManagerDelegate`
113 | func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
114 | subsribedCentrals.append(central)
115 | }
116 |
117 | /// - SeeAlso: `CBPeripheralManagerDelegate`
118 | func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) {
119 | guard let index = subsribedCentrals.firstIndex(where: { $0 === central }) else { return }
120 | subsribedCentrals.remove(at: index)
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Framework/Source Files/Advertisement/BluetoothAdvertisement.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 |
8 | /// A public facing class used to setup device as an advertising Bluetooth peripheral.
9 | public final class BluetoothAdvertisement {
10 |
11 | /// Advertisement service.
12 | /// - SeeAlso: `AdvertisementService`
13 | private lazy var advertisementService = AdvertisementService()
14 |
15 | /// A singleton instance.
16 | public static let shared = BluetoothAdvertisement()
17 |
18 | /// Default initializer
19 | public init() { }
20 |
21 | /// Start advertising peripheral with parameters given by a configuration of passed peripheral.
22 | ///
23 | /// - Parameters:
24 | /// - peripheral: a peripheral containing configuration with specified services and characteristics.
25 | /// - errorHandler: an error handler. Will be called only after unsuccessfull advertisement setup.
26 | /// - SeeAlso: `AdvertisementError`
27 | /// - SeeAlso: `Peripheral`
28 | public func advertise(peripheral: Peripheral, errorHandler: ((AdvertisementError) -> ())?) {
29 | advertisementService.startAdvertising(peripheral, errorHandler: errorHandler)
30 | }
31 |
32 | /// Stops advertising peripheral.
33 | public func stopAdvertising() {
34 | advertisementService.stopAdvertising()
35 | }
36 |
37 | /// Updates a value for specified characteristic with data.
38 | /// After the request a notify will be called on all subscribed centrals.
39 | ///
40 | /// - Parameters:
41 | /// - command: a comand to update on characteristic.
42 | /// - characteristic: specified characteristic to be updated.
43 | /// - errorHandler: an error handler called if data update fails.
44 | /// - SeeAlso: `AdvertisementError`
45 | /// - SeeAlso: `Characteristic`
46 | public func update(_ command: Command, characteristic: Characteristic, errorHandler: @escaping (AdvertisementError) -> (Void)) {
47 | do {
48 | try advertisementService.updateValue(command.convertedData(), characteristic: characteristic, errorHandler: errorHandler)
49 | } catch {
50 | errorHandler(.incorrectUpdateData)
51 | }
52 | }
53 |
54 | /// Caled when a connected central submits a write reuqest to a specified characteristic.
55 | public var writeRequestCallback: ((Characteristic, Data?) -> ())? {
56 | didSet {
57 | advertisementService.writeCallback = writeRequestCallback
58 | }
59 | }
60 |
61 | /// Called when a connected central submits a read request to a specified characteristic.
62 | public var readRequestCallback: ((Characteristic) -> (Data))? {
63 | didSet {
64 | advertisementService.readCallback = readRequestCallback
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Framework/Source Files/Command/Command.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 |
7 | import Foundation
8 |
9 | /// Command enum - a handy wrapper for creating requests to peripheral devices.
10 | /// Handles creation of data with length according to passed parameter type, also creates data straight from hexadecimal
11 | /// string with exact the same value. If none of the cases matches needed type, use .data(Data) case where Data object can be passed directly.
12 | public enum Command {
13 | case int8(UInt8)
14 | case int16(UInt16)
15 | case int32(UInt32)
16 | case utf8String(String)
17 | case hexString(String)
18 | case data(Data)
19 |
20 | /// An error triggered when data parse fails.
21 | public enum ConversionError: Error {
22 | case incorrectInputFormat
23 | }
24 | }
25 |
26 | internal extension Command {
27 |
28 | /// Variable used for conversion of parameters to Data possible to write to peripheral.
29 | /// - Throws: Command.ConversionError
30 | func convertedData() throws -> Data {
31 | switch self {
32 | case .int8(let number):
33 | return number.decodedData
34 | case .int16(let number):
35 | return number.decodedData
36 | case .int32(let number):
37 | return number.decodedData
38 | case .utf8String(let string):
39 | guard let data = string.data(using: .utf8, allowLossyConversion: false) else {
40 | throw ConversionError.incorrectInputFormat
41 | }
42 | return data
43 | case .hexString(let string):
44 | return try string.hexDecodedData()
45 | case .data(let data):
46 | return data
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Framework/Source Files/Connection/BluetoothConnection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | import CoreBluetooth
8 |
9 | /// Public facing interface granting methods to connect and disconnect devices.
10 | public final class BluetoothConnection: NSObject {
11 |
12 | /// A singleton instance.
13 | public static let shared = BluetoothConnection()
14 |
15 | /// Connection service implementing native CoreBluetooth stack.
16 | private var connectionService = ConnectionService()
17 |
18 | /// An advertisement validation handler. Will be called upon every peripheral discovery. Return value from this closure
19 | /// will indicate if manager should or shouldn't start connection with the passed peripheral according to it's identifier
20 | /// and advertising packet.
21 | @available(*, deprecated, message: "This closure will be removed in future version. Please use `peripheralValidationHandler`.")
22 | public var advertisementValidationHandler: ((Peripheral, String, [String: Any]) -> (Bool))? {
23 | didSet {
24 | connectionService.advertisementValidationHandler = advertisementValidationHandler
25 | }
26 | }
27 |
28 | /// A peripheral validation handler. Will be called upon every peripheral discovery. Contains matched peripheral,
29 | /// discovered peripheral from CoreBluetooth, advertisement data and RSSI value. Return value from this closure
30 | /// will indicate if manager should or shouldn't start connection with the passed peripheral according to it's identifier
31 | /// and advertising packet.
32 | public var peripheralValidationHandler: ((Peripheral, CBPeripheral, [String: Any], NSNumber) -> (Bool))? {
33 | didSet {
34 | connectionService.peripheralValidationHandler = peripheralValidationHandler
35 | }
36 | }
37 |
38 | /// Callback for update to Bluetooth sensor state for current device.
39 | /// Assign custom block to this property to monitor Central Manager state changes (`poweredOn`, `poweredOff`, `unauthorized` etc.).
40 | public var centralManagerStateUpdateHandler: ((CBManagerState) -> Void)? {
41 | didSet {
42 | connectionService.centralManagerStateUpdateHandler = centralManagerStateUpdateHandler
43 | }
44 | }
45 |
46 | /// A peripheral connection cancelled handler. Called when disconnecting a peripheral using `disconnect(_:)` is completed.
47 | /// Contains matched peripheral and native peripheral from CoreBluetooth.
48 | public var peripheralConnectionCancelledHandler: ((Peripheral, CBPeripheral) -> Void)? {
49 | didSet {
50 | connectionService.peripheralConnectionCancelledHandler = peripheralConnectionCancelledHandler
51 | }
52 | }
53 |
54 | /// Current Bluetooth authorization status.
55 | public var bluetoothAuthorizationStatus: BluetoothAuthorizationStatus {
56 | connectionService.bluetoothAuthorizationStatus
57 | }
58 |
59 | /// Primary method used to connect to a device. Can be called multiple times to connect more than on device at the same time.
60 | ///
61 | /// - Parameters:
62 | /// - peripheral: a configured device you wish to connect to.
63 | /// - handler: a completion handler called upon successfull connection or a error.
64 | /// - SeeAlso: `BluetoothConnection.ConnectionError`
65 | /// - SeeAlso: `Peripheral`
66 | public func connect(_ peripheral: Peripheral, handler: ((ConnectionError?) -> ())?) {
67 | guard !peripheral.isConnected else {
68 | handler?(.deviceAlreadyConnected)
69 | return
70 | }
71 | guard !connectionService.exceededDevicesConnectedLimit else {
72 | handler?(.deviceConnectionLimitExceed)
73 | return
74 | }
75 | connectionService.connect(peripheral) { (peripheral, error) in
76 | guard peripheral === peripheral else { return }
77 | handler?(error)
78 | }
79 | }
80 |
81 | /// Primary method to disconnect a device. If it's not yet connected it'll be removed from connection queue, and connection attempts will stop.
82 | ///
83 | /// - Parameter peripheral: a peripheral you wish to disconnect. Should be exactly the same instance that was used for connection.
84 | public func disconnect(_ peripheral: Peripheral) {
85 | guard let cbPeripheral = peripheral.peripheral else {
86 | connectionService.remove(peripheral)
87 | return
88 | }
89 | connectionService.disconnect(cbPeripheral)
90 | }
91 |
92 | /// Function called to stop scanning for devices.
93 | public func stopScanning() {
94 | connectionService.stopScanning()
95 | }
96 |
97 | /// Requests User for authorization to use Bluetooth.
98 | public func requestBluetoothAuthorization() {
99 | connectionService.requestBluetoothAuthorization()
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/Framework/Source Files/Connection/ConnectionService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | import CoreBluetooth
8 |
9 | /// A wrapper around CoreBluetooth delegate stack.
10 | internal final class ConnectionService: NSObject {
11 |
12 | /// Closure used to check given peripheral against advertisement packet of discovered peripheral.
13 | internal var advertisementValidationHandler: ((Peripheral, String, [String: Any]) -> (Bool))? = { _,_,_ in return true }
14 |
15 | /// Closure used to check given peripheral against advertisement packet of discovered peripheral.
16 | internal var peripheralValidationHandler: ((Peripheral, CBPeripheral, [String: Any], NSNumber) -> (Bool))? = { _,_,_,_ in return true }
17 |
18 | /// Closure used to manage connection success or failure.
19 | internal var connectionHandler: ((Peripheral, ConnectionError?) -> ())?
20 |
21 | /// Closure called when disconnecting a peripheral using `disconnect(_:)` is completed.
22 | internal var peripheralConnectionCancelledHandler: ((Peripheral, CBPeripheral) -> ())?
23 |
24 | /// Called when Bluetooth sensor state for current device was updated.
25 | internal var centralManagerStateUpdateHandler: ((CBManagerState) -> Void)?
26 |
27 | /// Returns the amount of devices already connected.
28 | internal var connectedDevicesAmount: Int {
29 | return peripherals.read { storage in
30 | storage
31 | .filter { $0.isConnected }
32 | .count
33 | }
34 | }
35 |
36 | /// Indicates whether connected devices limit has been exceeded.
37 | internal var exceededDevicesConnectedLimit: Bool {
38 | return connectedDevicesAmount >= deviceConnectionLimit
39 | }
40 |
41 | /// Current Bluetooth authorization status.
42 | internal var bluetoothAuthorizationStatus: BluetoothAuthorizationStatus {
43 | if #available(iOS 13.1, *) {
44 | return CBManager.authorization.bluetoothAuthorizationStatus
45 | } else if #available(iOS 13.0, *) {
46 | return centralManager.authorization.bluetoothAuthorizationStatus
47 | } else {
48 | // Until iOS 12 applications could access Bluetooth without the user’s authorization
49 | return .allowedAlways
50 | }
51 | }
52 |
53 | /// Maximum amount of devices capable of connecting to a iOS device.
54 | private let deviceConnectionLimit = 8
55 |
56 | /// Set of peripherals the manager should connect.
57 | private var peripherals: SynchronizedArray> = []
58 |
59 | /// Handle to peripherals which were requested to disconnect.
60 | private var peripheralsToDisconnect: SynchronizedArray> = []
61 |
62 | private weak var connectingPeripheral: Peripheral?
63 |
64 | /// Connection options - means you will be notified on connection and disconnection of devices.
65 | private lazy var connectionOptions = [CBConnectPeripheralOptionNotifyOnConnectionKey: true,
66 | CBConnectPeripheralOptionNotifyOnDisconnectionKey: true]
67 |
68 | /// Scanning options. Means that one device can be discovered multiple times without connecting.
69 | private lazy var scanningOptions = [CBCentralManagerScanOptionAllowDuplicatesKey : true]
70 |
71 | /// CBCentralManager instance. Allows peripheral connection.
72 | /// iOS displays Bluetooth authorization popup when `CBCentralManager` is instantiated and authorization status is not determined.
73 | private lazy var centralManager: CBCentralManager = {
74 | let manager = CBCentralManager()
75 | manager.delegate = self
76 | return manager
77 | }()
78 |
79 | /// Set of advertisement UUID central manager should scan for.
80 | private var scanParameters: Set = Set()
81 | }
82 |
83 | extension ConnectionService {
84 |
85 | /// Starts connection with passed device. Connection result is passed in handler closure.
86 | internal func connect(_ peripheral: Peripheral, handler: @escaping (Peripheral, ConnectionError?) -> ()) {
87 | if connectionHandler == nil {
88 | connectionHandler = handler
89 | }
90 | do {
91 | try centralManager.validateState()
92 | peripherals.append(peripheral)
93 | reloadScanning()
94 | } catch let error {
95 | if let error = error as? BluetoothError {
96 | handler(peripheral, .bluetoothError(error))
97 | }
98 | }
99 | }
100 |
101 | /// Disconnects given device.
102 | internal func disconnect(_ peripheral: CBPeripheral) {
103 | peripherals.write { storage in
104 | guard let index = storage.firstIndex(where: { $0.peripheral === peripheral }) else { return }
105 | let peripheralToDisconnect = storage.remove(at: index)
106 | self.peripheralsToDisconnect.append(peripheralToDisconnect)
107 | }
108 | centralManager.cancelPeripheralConnection(peripheral)
109 | }
110 |
111 | /// Function called to remove peripheral from queue
112 | /// - Parameter peripheral: peripheral to remove.
113 | internal func remove(_ peripheral: Peripheral) {
114 | peripherals.write { storage in
115 | guard let index = storage.firstIndex(where: { $0 === peripheral }) else { return }
116 | storage.remove(at: index)
117 | }
118 | }
119 |
120 | /// Function called to stop scanning for devices.
121 | internal func stopScanning() {
122 | centralManager.stopScan()
123 | }
124 |
125 | /// Triggers showing Bluetooth authorization popup by instantiating the central manager.
126 | internal func requestBluetoothAuthorization() {
127 | _ = centralManager
128 | }
129 | }
130 |
131 | private extension ConnectionService {
132 |
133 | /// Reloads scanning if necessary. Adding scan parameters should be ommited in case of possible peripheral retrive.
134 | /// In that case connection is available without previous scanning.
135 | private func reloadScanning() {
136 | if centralManager.isScanning {
137 | centralManager.stopScan()
138 | }
139 | performDeviceAutoReconnection()
140 | let params: [CBUUID] = peripherals.read { storage in
141 | storage.compactMap { (peripheral) -> CBUUID? in
142 | guard peripheral.peripheral == nil else { return nil }
143 | return peripheral.configuration.advertisementUUID
144 | }
145 | }
146 | guard params.count != 0 else {
147 | return
148 | }
149 | scanParameters = Set(params)
150 | guard case .poweredOn = centralManager.state else { return }
151 | centralManager.scanForPeripherals(withServices: Array(scanParameters), options: scanningOptions)
152 | }
153 |
154 | /// Tries a peripeheral retrieve for each peripheral model. Peripheral can be retrieved in case the parameter of
155 | /// deviceIdentifier was passed during initialization. If it's correctly retrieved, scanning is unnecessary and peripheral
156 | /// can be directly connected.
157 | private func performDeviceAutoReconnection() {
158 | peripherals.read { storage in
159 | let identifiers = storage
160 | .filter { !$0.isConnected }
161 | .compactMap { UUID(uuidString: $0.deviceIdentifier ?? "") }
162 | guard !identifiers.isEmpty else { return }
163 | let retrievedPeripherals = centralManager.retrievePeripherals(withIdentifiers: identifiers)
164 | let matching = storage.matchingElementsWith(retrievedPeripherals)
165 | matching.forEach { (peripheral, cbPeripheral) in
166 | peripheral.peripheral = cbPeripheral
167 | centralManager.connect(cbPeripheral, options: connectionOptions)
168 | }
169 | }
170 | }
171 | }
172 |
173 | extension ConnectionService: CBCentralManagerDelegate {
174 |
175 | /// Determines Bluetooth sensor state for current device.
176 | /// - SeeAlso: CBCentralManagerDelegate
177 | public func centralManagerDidUpdateState(_ central: CBCentralManager) {
178 | centralManagerStateUpdateHandler?(central.state)
179 |
180 | guard let handler = connectionHandler,
181 | let anyDevice = peripherals.first
182 | else { return }
183 |
184 | do {
185 | try central.validateState()
186 | reloadScanning()
187 | } catch let error {
188 | if let error = error as? BluetoothError {
189 | handler(anyDevice, .bluetoothError(error))
190 | }
191 | }
192 | }
193 |
194 | /// Called when a peripheral with desired advertised service is discovered.
195 | /// - SeeAlso: CBCentralManagerDelegate
196 | public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
197 | let matchingPeripheral = peripherals.read { storage in
198 | storage
199 | .filter({ $0.configuration.matches(advertisement: advertisementData)})
200 | .first(where: { $0.peripheral == nil })
201 | }
202 |
203 | guard let handler = peripheralValidationHandler,
204 | let matchingPeripheral,
205 | connectingPeripheral == nil,
206 | handler(matchingPeripheral, peripheral, advertisementData, RSSI)
207 | else { return }
208 |
209 | connectingPeripheral = matchingPeripheral
210 | connectingPeripheral?.peripheral = peripheral
211 | connectingPeripheral?.rssi = RSSI
212 | central.connect(peripheral, options: connectionOptions)
213 | if exceededDevicesConnectedLimit {
214 | centralManager.stopScan()
215 | }
216 | }
217 |
218 | /// Called upon a successfull peripheral connection.
219 | /// - SeeAlso: CBCentralManagerDelegate
220 | public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
221 | let connectingPeripheral = peripherals.read { $0.first(withIdentical: peripheral) }
222 | guard let connectingPeripheral else {
223 | // Central manager did connect to a peripheral, which is not on the list of allowed peripherals at this moment.
224 | // Peripheral might have re-connected unexpectedly. Disconnect it, so it can be discovered.
225 | centralManager.cancelPeripheralConnection(peripheral)
226 | return
227 | }
228 | self.connectingPeripheral = connectingPeripheral
229 | connectingPeripheral.peripheral = peripheral
230 | peripheral.delegate = self
231 | peripheral.discoverServices(connectingPeripheral.configuration.services.map({ $0.bluetoothUUID }))
232 | }
233 |
234 | /// Called when peripheral connection fails on its initialization, we'll reconnect it right away.
235 | /// - SeeAlso: CBCentralManagerDelegate
236 | public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
237 | central.connect(peripheral, options: connectionOptions)
238 | }
239 | }
240 |
241 | extension ConnectionService: CBPeripheralDelegate {
242 |
243 | /// Called upon discovery of services of a connected peripheral. Used to map model services to passed configuration and
244 | /// discover characteristics for each matching service.
245 | /// - SeeAlso: CBPeripheralDelegate
246 | public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
247 | guard let services = peripheral.services, error == nil else { return }
248 | let matching = connectingPeripheral?.configuration.services.matchingElementsWith(services)
249 | guard matching?.count != 0 else {
250 | centralManager.cancelPeripheralConnection(peripheral)
251 | return
252 | }
253 | matching?.forEach({ (service, cbService) in
254 | peripheral.discoverCharacteristics(service.characteristics.map({ $0.bluetoothUUID }), for: cbService)
255 | })
256 | }
257 |
258 | /// Called upon discovery of characteristics of a connected peripheral per each passed service. Used to map CBCharacteristic
259 | /// instances to passed configuration, assign characteristic raw values and setup notifications.
260 | /// - SeeAlso: CBPeripheralDelegate
261 | public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
262 | guard let characteristics = service.characteristics, error == nil else { return }
263 | let matchingService = connectingPeripheral?.configuration.services.first(where: { $0.bluetoothUUID == service.uuid })
264 | let matchingCharacteristics = matchingService?.characteristics.matchingElementsWith(characteristics)
265 | guard matchingCharacteristics?.count != 0 else {
266 | centralManager.cancelPeripheralConnection(peripheral)
267 | return
268 | }
269 | matchingCharacteristics?.forEach({ (tuple) in
270 | let (characteristic, cbCharacteristic) = tuple
271 | characteristic.setRawCharacteristic(cbCharacteristic)
272 | peripheral.setNotifyValue(characteristic.isObservingValue, for: cbCharacteristic)
273 | })
274 | if let connectingPeripheral = self.connectingPeripheral {
275 | connectingPeripheral.peripheral?.delegate = connectingPeripheral
276 | self.connectionHandler?(connectingPeripheral, nil)
277 | }
278 | connectingPeripheral = nil
279 | }
280 |
281 | /// Called when device is disconnected.
282 | /// If connection was cancelled using `disconnect(_:)`, then `peripheralConnectionCancelledHandler(_:)` is called.
283 | /// Otherwise device is reconnected. Connect method does not have a timeout, so connection will be triggered
284 | /// anytime in the future when the device is discovered. In case the connection is no longer needed we'll just return.
285 | /// - SeeAlso: CBPeripheralDelegate
286 | public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
287 | /// `error` is nil if disconnect resulted from a call to `cancelPeripheralConnection(_:)`.
288 | /// SeeAlso: https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate/1518791-centralmanager
289 | if error == nil {
290 | peripheralsToDisconnect.write { [weak self] storage in
291 | guard let disconnectedPeripheral = storage.first(withIdentical: peripheral) else { return }
292 | storage.removeAll(where: { $0 === disconnectedPeripheral })
293 | self?.peripheralConnectionCancelledHandler?(disconnectedPeripheral, peripheral)
294 | }
295 | return
296 | }
297 |
298 | if let disconnectedPeripheral = peripherals.read({ $0.first(withIdentical: peripheral) }),
299 | let nativePeripheral = disconnectedPeripheral.peripheral {
300 | disconnectedPeripheral.disconnectionHandler?()
301 | centralManager.connect(nativePeripheral, options: connectionOptions)
302 | }
303 | }
304 | }
305 |
--------------------------------------------------------------------------------
/Framework/Source Files/Extensions/Array.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | import CoreBluetooth
8 |
9 | internal extension Array {
10 |
11 | /// Utility method used for matching elements from two different arrays and returning an array of tuples. Matching is based
12 | /// on a return value from comparison handler.
13 | func matchingElementsWith(_ array: [T], comparison: (Element, T) -> Bool) -> [(Element, T)] {
14 | return compactMap { element in
15 | guard let index = array.firstIndex(where: { comparison(element, $0) }) else { return nil }
16 | return (element, array[index])
17 | }
18 | }
19 | }
20 |
21 | internal extension Array where Element == Service {
22 |
23 | /// Convenience method for easier matching of Service and CBService objects.
24 | func matchingElementsWith(_ array: [CBService]) -> [(Service, CBService)] {
25 | return matchingElementsWith(array, comparison: { (service, cbService) -> Bool in
26 | return cbService.uuid == service.bluetoothUUID
27 | })
28 | }
29 | }
30 |
31 | internal extension Array where Element == Characteristic {
32 |
33 | /// Convenience method for easier matching of Characteristic and CBCharacteristic objects.
34 | func matchingElementsWith(_ array: [CBCharacteristic]) -> [(Characteristic, CBCharacteristic)] {
35 | return matchingElementsWith(array, comparison: { (characteristic, cbCharacteristic) -> Bool in
36 | return characteristic.bluetoothUUID == cbCharacteristic.uuid
37 | })
38 | }
39 | }
40 |
41 | internal extension Array where Element == Peripheral {
42 |
43 | /// Convenience method for easier matching of Peripheral and CBPeripheral objects.
44 | func matchingElementsWith(_ array: [CBPeripheral]) -> [(Peripheral, CBPeripheral)] {
45 | return matchingElementsWith(array, comparison: { (peripheral, cbPeripheral) -> Bool in
46 | return peripheral.deviceIdentifier == cbPeripheral.identifier.uuidString
47 | })
48 | }
49 | }
50 |
51 | internal extension Array where Element == AdvertisementData {
52 |
53 | /// Convenience method used to generate advertising packet from array of AdvertisementData objects.
54 | func combined() -> [String: Any] {
55 | var dictionary = [String: Any]()
56 | forEach {
57 | guard let data = $0.data else { return }
58 | var assignedValue: Any = $0
59 | if let currentValue = dictionary[$0.key], let newValue = data as? [Any] {
60 | if var arrayValue = currentValue as? [Any] {
61 | arrayValue.append(contentsOf: newValue)
62 | assignedValue = arrayValue
63 | } else {
64 | assignedValue = [data, currentValue]
65 | }
66 | } else {
67 | assignedValue = data
68 | }
69 | dictionary[$0.key] = assignedValue
70 | }
71 | return dictionary
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Framework/Source Files/Extensions/CBCharacteristic.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | import CoreBluetooth.CBCharacteristic
8 |
9 | internal extension CBCharacteristic {
10 |
11 | /// Validates if given characteristic is readable.
12 | func validateForRead() -> Bool {
13 | return properties.contains(.read)
14 | }
15 |
16 | /// Validates if given characteristic is writeable.
17 | func validateForWrite() -> Bool {
18 | return properties.contains(.write) || properties.contains(.writeWithoutResponse)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Framework/Source Files/Extensions/CBManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | import CoreBluetooth
8 |
9 | internal extension CBManager {
10 |
11 | /// Validates the current state of CBManager class to determine if Bluetooth is not supported on this device or is turned off or unavailable for some other reason.
12 | func validateState() throws {
13 | switch state {
14 | case .poweredOff, .resetting:
15 | throw BluetoothError.bluetoothUnavailable
16 | case .unauthorized:
17 | throw BluetoothError.unauthorized
18 | case .unsupported, .unknown:
19 | throw BluetoothError.incompatibleDevice
20 | default:
21 | break
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Framework/Source Files/Extensions/CBUUID.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | import CoreBluetooth.CBUUID
8 |
9 | internal extension CBUUID {
10 |
11 | /// Error for creating a CBUUID with string invalid to UUID standards.
12 | enum CreationError: Error {
13 | case invalidString
14 | }
15 |
16 | /// Convenience initializer, a wrapper for default init(string: String) method with error handling, not crashing
17 | /// like default one.
18 | ///
19 | /// - Parameter uuidString: a String wished to be converted into CBUIID.
20 | /// - Throws: `CreationError.invalidString` if passed String is not valid.
21 | convenience init(uuidString: String) throws {
22 | guard let uuid = UUID(uuidString: uuidString) else {
23 | guard uuidString.isValidShortenedUUID() else { throw CreationError.invalidString }
24 | self.init(string: uuidString)
25 | return
26 | }
27 | self.init(nsuuid: uuid)
28 | }
29 | }
30 |
31 | /// Handy extension used to be able to compare CBUUID's with == operator.
32 | extension CBUUID: Comparable {
33 | public static func <(lhs: CBUUID, rhs: CBUUID) -> Bool {
34 | return lhs.uuidString == rhs.uuidString
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Framework/Source Files/Extensions/Data.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 |
8 | /// Data extension allowing conversion from Data into String containing exactly the same bytes.
9 | internal extension Data {
10 |
11 | /// Returns encoded String.
12 | var hexEncodedString: String {
13 | return map { String(format: "%02hhX", $0) }.joined()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Framework/Source Files/Extensions/Int.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 |
8 | /// Extension for signed integers allowing conversion to Data with proper size.
9 | internal extension UnsignedInteger {
10 |
11 | /// Returns decoded data with proper size.
12 | var decodedData: Data {
13 | return withUnsafeBytes(of: self) { Data($0) }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Framework/Source Files/Extensions/Sequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Sequence.swift
3 | // Bluetooth
4 | //
5 | // Created by Filip Zieliński on 08/04/2022.
6 | // Copyright © 2022 Netguru. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreBluetooth
11 |
12 | internal extension Sequence {
13 |
14 | /// Returns the first element of the sequence where given element property is identical (`===`) to given object instance.
15 | /// - Parameters:
16 | /// - propertyKeyPath: a key path to an element property.
17 | /// - instance: an object instance to compare against.
18 | /// - Returns: The first element of the sequence where given element property is identical (`===`) to given object instance, or `nil` if there is no such element.
19 | func first(where propertyKeyPath: KeyPath, isIdenticalTo instance: Object) -> Element? {
20 | first { $0[keyPath: propertyKeyPath] === instance }
21 | }
22 |
23 | /// Returns the first element of the sequence where given element optional property is identical (`===`) to given object instance.
24 | /// - Parameters:
25 | /// - propertyKeyPath: a key path to an element property.
26 | /// - instance: an object instance to compare against.
27 | /// - Returns: The first element of the sequence where given element optional property is identical (`===`) to given object instance, or `nil` if there is no such element.
28 | func first(where propertyKeyPath: KeyPath, isIdenticalTo instance: Object) -> Element? {
29 | first { $0[keyPath: propertyKeyPath] === instance }
30 | }
31 | }
32 |
33 | internal extension Sequence where Element == Peripheral {
34 |
35 | /// Convenience method returning the first peripheral of the sequence with `peripheral` property identical (`===`) to given `CBPeripheral` instance.
36 | /// - Parameter cbPeripheral: a `CBPeripheral` instance.
37 | /// - Returns: The first peripheral of the sequence with `peripheral` property identical (`===`) to given `CBPeripheral` instance, or `nil` if there is no such element.
38 | func first(withIdentical cbPeripheral: CBPeripheral) -> Element? {
39 | first(where: \.peripheral, isIdenticalTo: cbPeripheral)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Framework/Source Files/Extensions/String.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 |
8 | internal extension String {
9 |
10 | /// Validates if String is a proper shoretened UUID which means its 4 or 6 characters long and contains only hexadecimal characters.
11 | func isValidShortenedUUID() -> Bool {
12 | var evaluation = self
13 | if hasPrefix("0x") {
14 | evaluation = evaluation.replacingOccurrences(of: "0x", with: "")
15 | }
16 | return isHexadecimal && (evaluation.count == 4 || evaluation.count == 6)
17 | }
18 |
19 | /// Checks if string contains only hexadecimal characters.
20 | var isHexadecimal: Bool {
21 | let invertedHexCharacterSet = NSCharacterSet(charactersIn: "0123456789ABCDEF").inverted
22 | return uppercased().rangeOfCharacter(from: invertedHexCharacterSet) == nil
23 | }
24 | }
25 |
26 | /// String extension allowing conversion of strings like 0x2A01 into Data with the same format.
27 | internal extension String {
28 |
29 | /// Returns Data with decoded string.
30 | func hexDecodedData() throws -> Data {
31 | guard isHexadecimal else {
32 | throw Command.ConversionError.incorrectInputFormat
33 | }
34 | var data = Data(capacity: count / 2)
35 | let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
36 | regex.enumerateMatches(in: self, range: NSMakeRange(0, utf16.count)) { match, _, _ in
37 | guard let nsRange = match?.range, let range = Range(nsRange, in: self) else { return }
38 | let byteString = self[range]
39 | guard var num = UInt8(byteString, radix: 16) else { return }
40 | data.append(&num, count: 1)
41 | }
42 | return data
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Framework/Source Files/Model/AdvertisementData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | import CoreBluetooth
8 |
9 | /// An enum used to specify advertising parameters of a peripheral.
10 | public enum AdvertisementData {
11 |
12 | case localName(String)
13 | case servicesUUIDs(String)
14 | case serviceData(Data)
15 | case txPower(Int)
16 | case manufacturersData(String)
17 | case custom(String, Any)
18 | }
19 |
20 | internal extension AdvertisementData {
21 |
22 | var data: Any? {
23 | switch self {
24 | case .localName(let name):
25 | return name
26 | case .servicesUUIDs(let uuid):
27 | return try? [CBUUID(uuidString: uuid)]
28 | case .serviceData(let data):
29 | return data
30 | case .txPower(let level):
31 | return level
32 | case .manufacturersData(let data):
33 | return data
34 | case .custom(_, let data):
35 | return data
36 | }
37 | }
38 |
39 | var key: String {
40 | switch self {
41 | case .localName(_):
42 | return CBAdvertisementDataLocalNameKey
43 | case .servicesUUIDs(_):
44 | return CBAdvertisementDataServiceUUIDsKey
45 | case .serviceData(_):
46 | return CBAdvertisementDataServiceDataKey
47 | case .txPower(_):
48 | return CBAdvertisementDataTxPowerLevelKey
49 | case .manufacturersData(_):
50 | return CBAdvertisementDataManufacturerDataKey
51 | case .custom(let key, _):
52 | return key
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Framework/Source Files/Model/BluetoothAuthorizationStatus.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import CoreBluetooth
7 |
8 | /// The current authorization state of a Core Bluetooth manager.
9 | @objc public enum BluetoothAuthorizationStatus: Int {
10 | /// Bluetooth authorization status is not determined.
11 | case notDetermined = 0
12 |
13 | /// Bluetooth connection is restricted.
14 | case restricted = 1
15 |
16 | /// Bluetooth connection is denied.
17 | case denied = 2
18 |
19 | /// Bluetooth connection is allowed always.
20 | case allowedAlways = 3
21 | }
22 |
23 | extension BluetoothAuthorizationStatus {
24 |
25 | /// `CBManagerAuthorization` representation of current authorization status.
26 | @available(iOS 13.0, *)
27 | var cbManagerAuthorization: CBManagerAuthorization {
28 | switch self {
29 | case .notDetermined:
30 | return .notDetermined
31 | case .restricted:
32 | return .restricted
33 | case .denied:
34 | return .denied
35 | case .allowedAlways:
36 | return .allowedAlways
37 | }
38 | }
39 | }
40 |
41 | @available(iOS 13.0, *)
42 | extension CBManagerAuthorization {
43 |
44 | /// `BluetoothAuthorizationStatus` representation of current authorization status.
45 | @available(iOSApplicationExtension 13.0, *)
46 | var bluetoothAuthorizationStatus: BluetoothAuthorizationStatus {
47 | switch self {
48 | case .notDetermined:
49 | return .notDetermined
50 | case .restricted:
51 | return .restricted
52 | case .denied:
53 | return .denied
54 | case .allowedAlways:
55 | return .allowedAlways
56 | @unknown default:
57 | return .notDetermined
58 | }
59 | }
60 | }
61 |
62 | extension BluetoothAuthorizationStatus: CustomStringConvertible {
63 |
64 | /// SeeAlso: `CustomStringConvertible.description`
65 | public var description: String {
66 | switch self {
67 | case .notDetermined:
68 | return "notDetermined"
69 | case .restricted:
70 | return "restricted"
71 | case .denied:
72 | return "denied"
73 | case .allowedAlways:
74 | return "allowedAlways"
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Framework/Source Files/Model/BluetoothError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 |
8 | /// List of possible general Bluetooth failures.
9 | public enum BluetoothError: Error {
10 | case bluetoothUnavailable
11 | case incompatibleDevice
12 | case unauthorized
13 | }
14 |
15 | /// List of possible errors during advertisement.
16 | public enum AdvertisementError: Error {
17 | case bluetoothError(BluetoothError)
18 | case deviceNotAdvertising
19 | case incorrectUpdateData
20 | case otherError(Error)
21 | }
22 |
23 | /// List of possible errors during connection.
24 | public enum ConnectionError: Error {
25 | case bluetoothError(BluetoothError)
26 | case deviceConnectionLimitExceed
27 | case deviceAlreadyConnected
28 | }
29 |
--------------------------------------------------------------------------------
/Framework/Source Files/Model/Characteristic.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | import CoreBluetooth
8 |
9 | /// Struct wrapping Apple's native CBCharacteristic class. Used to create Configuration to connect with peripheral.
10 | /// It presents a clear interface for interacting with characteristics providing notify property.
11 | public class Characteristic {
12 |
13 | /// UUID of desired Characteristic.
14 | public let uuid: String
15 |
16 | /// A bool indicating if isNotifying value should be set on a characteristic upon discovery.
17 | public let isObservingValue: Bool
18 |
19 | /// A bool indicating a peripheral can write the characteristic’s value with a response.
20 | public var isWriteWithResponse: Bool? {
21 | return rawCharacteristic?.properties.contains(.write)
22 | }
23 |
24 | /// A handler indicating characteristic value update events.
25 | public var notifyHandler: ((Data?) -> ())?
26 |
27 | /// Raw characteristics value filled after connection.
28 | internal var rawCharacteristic: CBCharacteristic?
29 |
30 | /// Raw mutable characteristic, assigned for advertisement needs.
31 | internal var advertisementCharacteristic: CBMutableCharacteristic?
32 |
33 | /// CBUUID parsed from passed UUID String.
34 | internal let bluetoothUUID: CBUUID
35 |
36 | /// Initializes a new instance of Characteristic. It's failable if passed UUID String is not parseable to UUID standards.
37 | ///
38 | /// - Parameters:
39 | /// - uuid: UUID of desired service, should be parseable to CBUUID in order for the initializer to work.
40 | /// - shouldObserveNotification: indicates if this characteristic should notify when it's value changes. Note that this will happen only when characteristic properties include Notify. False by default.
41 | /// - Throws: CBUUID.CreationError
42 | /// - SeeAlso: `CBUUID.CreationError`
43 | public init(uuid: String, shouldObserveNotification: Bool = false) throws {
44 | self.bluetoothUUID = try CBUUID(uuidString: uuid)
45 | self.uuid = uuid
46 | self.isObservingValue = shouldObserveNotification
47 | }
48 |
49 | /// Sets raw characteristic used for notifying purposes
50 | internal func setRawCharacteristic(_ characteristic: CBCharacteristic) {
51 | rawCharacteristic = characteristic
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Framework/Source Files/Model/Configuration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | import CoreBluetooth
8 |
9 | /// Configuration struct is used to create a complete representation of peripheral's services and characteristics.
10 | /// Use to initialize Perpiheral class.
11 | public struct Configuration {
12 |
13 | /// An array of services contained in configuration.
14 | public let services: [Service]
15 |
16 | /// Advertised UUID from initializer parsed to a CBUUID instance.
17 | internal let advertisementUUID: CBUUID
18 |
19 | /// Creates a new instance of configuration containing Services desired peripheral should contain.
20 | /// Used to initialize a Peripheral instance.
21 | ///
22 | /// - Parameters:
23 | /// - services: An array of Services wished to use.
24 | /// - advertisement: UUID of desired peripheral that is specified in advertisement header.
25 | /// - Throws: CBUUID.CreationError
26 | /// - SeeAlso: `CBUUID.CreationError`
27 | public init(services: [Service], advertisement: String) throws {
28 | advertisementUUID = try CBUUID(uuidString: advertisement)
29 | self.services = services
30 | }
31 | }
32 |
33 | internal extension Configuration {
34 |
35 | /// Helper method to check peripheral advertisement against one passed in conifguration.
36 | func matches(advertisement: [String: Any]) -> Bool {
37 | guard let uuids = advertisement[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] else { return false }
38 | for uuid in uuids {
39 | if uuid.uuidString.uppercased() == advertisementUUID.uuidString.uppercased() { return true }
40 | }
41 | return false
42 | }
43 |
44 | /// Helper method used to search characteristics wrapper for specified CBCharacteristic
45 | func characteristic(matching cbCharacteristic: CBCharacteristic) -> Characteristic? {
46 | let characteristics = services.flatMap { $0.characteristics }
47 | return characteristics.filter { $0.bluetoothUUID.uuidString == cbCharacteristic.uuid.uuidString }.first
48 | }
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/Framework/Source Files/Model/Peripheral/AdvertisablePeripheral.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | import CoreBluetooth
8 |
9 | public extension Peripheral where Type == Advertisable {
10 |
11 | /// Creates a new instance of Peripheral that will be used for advertisement purposes.
12 | ///
13 | /// - Parameters:
14 | /// - configuration: a specification of the peripheral that you are willing to advertise wrapped in Configuration object instance.
15 | /// - advertisementData: a data that should be put in Bluetooth LE advertisement header. Please note that iPhones don't allow some keys there, so they won't be advertised even if set properly.
16 | convenience init(configuration: Configuration, advertisementData: [AdvertisementData]) {
17 | self.init(configuration: configuration, deviceIdentifier: nil, advertisementData: advertisementData)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Framework/Source Files/Model/Peripheral/ConnectablePeripheral.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | import CoreBluetooth
8 |
9 | public extension Peripheral where Type == Connectable {
10 |
11 | /// Enum for distinguishing which transmission action was taken.
12 | private enum TransmissionAction {
13 | case read, write
14 | }
15 |
16 | /// Deafult initializer for Perpipheral.
17 | ///
18 | /// - Parameters:
19 | /// - configuration: previously created configuration containing all desired services and characteristics.
20 | /// - deviceIdentifier: optional parameter. If device identifier is cached locally then it should be passed here. When set, connection to peripheral is much quicker.
21 | /// - SeeAlso: `Configuration`
22 | convenience init(configuration: Configuration, deviceIdentifier: String? = nil) {
23 | self.init(configuration: configuration, deviceIdentifier: deviceIdentifier, advertisementData: nil)
24 | }
25 |
26 | /// Indicates if device is currently connected.
27 | var isConnected: Bool {
28 | guard let peripheral = peripheral else { return false }
29 | return peripheral.state == .connected
30 | }
31 |
32 | /// Method used for writing to the peripheral after it's connected.
33 | ///
34 | /// - Parameters:
35 | /// - command: a command to write to the device.
36 | /// - characteristic: a characteristic the command should be directed to.
37 | /// - handler: a completion handler indicating if reuqest was successfull.
38 | /// - type: type of write request
39 | /// - SeeAlso: `Command`
40 | /// - SeeAlso: `Characteristic`
41 | /// - SeeAlso: `CBCharacteristicWriteType`
42 | /// - SeeAlso: `Peripheral.TransmissionError`
43 | func write(command: Command, characteristic: Characteristic, type: CBCharacteristicWriteType = .withResponse, handler: ((TransmissionError?) -> ())?) {
44 | do {
45 | let unwrapped = try validateForTransmission(characteristic, action: .write)
46 | writeHandler = handler
47 | try peripheral?.writeValue(command.convertedData(), for: unwrapped, type: type)
48 | } catch let error {
49 | guard let conversionError = error as? Command.ConversionError else {
50 | handler?(error as? TransmissionError)
51 | return
52 | }
53 | handler?(TransmissionError.incorrectInputFormat(conversionError))
54 | }
55 | }
56 |
57 | /// Method used to perform read request from peripheral after it's connected.
58 | ///
59 | /// - Parameters:
60 | /// - characteristic: a characteristic you wish to read.
61 | /// - handler: completion handler returning Data retrieved from characteristic or error if it failed.
62 | /// - SeeAlso: `Characteristic`
63 | /// - SeeAlso: `Peripheral.TransmissionError`
64 | func read(_ characteristic: Characteristic, handler: ((Data?, TransmissionError?) -> ())?) {
65 | do {
66 | let unwrapped = try validateForTransmission(characteristic, action: .read)
67 | readHandler = handler
68 | peripheral?.readValue(for: unwrapped)
69 | } catch let error {
70 | handler?(nil, error as? TransmissionError)
71 | }
72 | }
73 |
74 | /// Method used to perform read RSSI request from peripheral.
75 | ///
76 | /// - Parameter handler: completion handler returning RSSI value retrieved from peripheral.
77 | func readRSSI(_ handler: ((NSNumber?, TransmissionError?) -> ())?) {
78 | guard isConnected else {
79 | handler?(nil, .deviceNotConnected)
80 | return
81 | }
82 | rssiHandler = handler
83 | peripheral?.readRSSI()
84 | }
85 |
86 | /// Performs a general validation if write or read requests can be performed on specified characteristic.
87 | private func validateForTransmission(_ characteristic: Characteristic, action: TransmissionAction) throws -> CBCharacteristic {
88 | guard isConnected else {
89 | throw TransmissionError.deviceNotConnected
90 | }
91 | guard let characteristic = characteristic.rawCharacteristic else {
92 | throw TransmissionError.characteristicNotDiscovered
93 | }
94 | if action == .read && !characteristic.validateForRead() {
95 | throw TransmissionError.invalidCharacteristicPermissions(characteristic.properties)
96 | }
97 | if action == .write && !characteristic.validateForWrite() {
98 | throw TransmissionError.invalidCharacteristicPermissions(characteristic.properties)
99 | }
100 | return characteristic
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/Framework/Source Files/Model/Peripheral/Peripheral.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | import CoreBluetooth
8 |
9 | /// A phantom type protocol making it easier to distinguish peripherals.
10 | public protocol PeripheralType {}
11 |
12 | /// A phantom type enums allowing to distinguish between Peripheral used for connection and advertisement.
13 | public enum Connectable: PeripheralType {}
14 | public enum Advertisable: PeripheralType {}
15 |
16 | /// Class wrapping native Apple's CBPeripheral class. Should be passed as connection parameter and initialized with a
17 | /// Configuration. It presents a clear interface for writing and reading interactions with remote peripherals adding closure reponses.
18 | public final class Peripheral: NSObject, CBPeripheralDelegate {
19 |
20 | /// List of errors possible to happen upon a write or read request.
21 | public enum TransmissionError: Error {
22 | case invalidCharacteristicPermissions(CBCharacteristicProperties)
23 | case characteristicNotDiscovered
24 | case deviceNotConnected
25 | case incorrectInputFormat(Command.ConversionError)
26 | case auxiliaryError(Error)
27 | }
28 |
29 | public override init() {
30 | fatalError("Init is unavailable, please use init(configuration:deviceIdentifier:) instead.")
31 | }
32 |
33 | internal init(configuration: Configuration, deviceIdentifier: String? = nil, advertisementData: [AdvertisementData]? = nil) {
34 | self.configuration = configuration
35 | self.deviceIdentifier = deviceIdentifier
36 | self.advertisementData = advertisementData
37 | }
38 |
39 | /// Configuration of services and characteristics desired peripheral should contain.
40 | public let configuration: Configuration
41 |
42 | /// A device parameter. Should be cached locally in order to pass for every connection after the first one.
43 | /// If passed, every connection should happen much quicker.
44 | public var deviceIdentifier: String?
45 |
46 | /// Name of the peripheral returned from Apple native peripheral class.
47 | public var name: String? {
48 | return peripheral?.name
49 | }
50 |
51 | /// Last received signal strength indicator of the peripheral, in decibels.
52 | public var rssi: NSNumber?
53 |
54 | /// Handler which will be called when device will be disconnected.
55 | public var disconnectionHandler: (() -> Void)?
56 |
57 | internal var advertisementData: [AdvertisementData]?
58 |
59 | /// Private instance of Apple native peripheral class. Used to manage write and read requests.
60 | internal var peripheral: CBPeripheral? {
61 | didSet {
62 | peripheral?.delegate = self
63 | }
64 | }
65 |
66 | /// Private variable for storing reference to write completion callback.
67 | internal var writeHandler: ((TransmissionError?) -> ())?
68 |
69 | /// Private variable for storing reference to read completion callback.
70 | internal var readHandler: ((Data?, TransmissionError?) -> ())?
71 |
72 | /// Private variable for storing reference to read rssi completion callback.
73 | internal var rssiHandler: ((NSNumber?, TransmissionError?) -> ())?
74 |
75 | /// Called after reading data from characteristic.
76 | /// - SeeAlso: `CBPeripheralDelegate`
77 | /// This should be moved to an extension in Swift 5 according to: https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md feature.
78 | public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
79 | defer {
80 | writeHandler = nil
81 | }
82 | guard let handler = writeHandler else { return }
83 | guard let error = error else {
84 | handler(nil)
85 | return
86 | }
87 | handler(.auxiliaryError(error))
88 | }
89 |
90 | /// Called in two cases:
91 | /// 1) After performing read request from peripheral.
92 | /// 2) After peripheral updates value for characteristic with notify turned on.
93 | /// - SeeAlso: `CBPeripheralDelegate`
94 | public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
95 | defer {
96 | readHandler = nil
97 | }
98 | // It's assumed that if someone performed a read request, we'll ignore calling a notification for this value.
99 | guard let handler = readHandler, error == nil else {
100 | let wrapped = configuration.characteristic(matching: characteristic)
101 | wrapped?.notifyHandler?(characteristic.value)
102 | return
103 | }
104 | guard let error = error else {
105 | handler(characteristic.value, nil)
106 | return
107 | }
108 | handler(nil, .auxiliaryError(error))
109 | }
110 |
111 | /// Called after reading RRSI value from peripheral.
112 | /// - SeeAlso: `CBPeripheralDelegate`
113 | public func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
114 | defer {
115 | rssiHandler = nil
116 | }
117 |
118 | guard let handler = rssiHandler else { return }
119 | guard let error = error else {
120 | rssi = RSSI
121 | handler(RSSI, nil)
122 | return
123 | }
124 | handler(nil, .auxiliaryError(error))
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Framework/Source Files/Model/Service.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | import CoreBluetooth
8 |
9 | /// Struct wrapping Apple's native CBService class. Used to create Configuration for this framework.
10 | public class Service {
11 |
12 | /// UUID of desired service.
13 | public let uuid: String
14 |
15 | /// A set of characteristics this service should contain.
16 | public let characteristics: [Characteristic]
17 |
18 | /// CBUUID parsed from passed UUID String.
19 | internal let bluetoothUUID: CBUUID
20 |
21 | /// Mutable characteristic used for advertisement.
22 | internal var advertisementService: CBMutableService?
23 |
24 | /// Initializes a new instance of Service. It's failable if passed UUID String is not parseable to UUID standards.
25 | ///
26 | /// - Parameters:
27 | /// - uuid: UUID of desired service, should be parseable to CBUUID in order for the initializer to work.
28 | /// - characteristics: a list of Characteristic desired Service should contain. Please note that this list does not have to be exhaustive and contain all characteristics desired service contains on the peripheral. Pass only ones
29 | /// you wish to use.
30 | /// - Throws: CBUUID.CreationError
31 | /// - SeeAlso: `CBUUID.CreationError`
32 | public init(uuid: String, characteristics: [Characteristic]) throws {
33 | self.bluetoothUUID = try CBUUID(uuidString: uuid)
34 | self.uuid = uuid
35 | self.characteristics = characteristics
36 | }
37 | }
38 |
39 | internal extension Service {
40 |
41 | /// Creates CBMutableService used for advertisement and assigns it to the local variable.
42 | func assignAdvertisementService() -> CBMutableService {
43 | let service = CBMutableService(type: bluetoothUUID, primary: true)
44 | var cbCharacteristics = [CBMutableCharacteristic]()
45 | characteristics.forEach { characteristic in
46 | let cbCharacteristc = CBMutableCharacteristic(type: characteristic.bluetoothUUID, properties: [.read, .write, .notify], value: nil, permissions: [.readable, .writeable])
47 | cbCharacteristics.append(cbCharacteristc)
48 | characteristic.advertisementCharacteristic = cbCharacteristc
49 | }
50 | service.characteristics = cbCharacteristics
51 | advertisementService = service
52 | return service
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Framework/Source Files/Utilities/DispatchQueueProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2023 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 |
8 | /// Protocol describing a `DispatchQueue`.
9 | protocol DispatchQueueProtocol: AnyObject {
10 |
11 | /// Submits a work item for execution and returns the results from that item after it finishes executing.
12 | /// - Parameter work: The block that contains the work to perform.
13 | /// - Returns: The return value of the item in the work parameter.
14 | func sync(execute work: () throws -> T) rethrows -> T
15 |
16 | /// Schedules a block asynchronously for execution
17 | /// - Parameters:
18 | /// - flags: Additional attributes to apply when executing the block. For a list of possible values, see `DispatchWorkItemFlags`.
19 | /// - work: The block containing the work to perform. This block has no return value and no parameters.
20 | func async(flags: DispatchWorkItemFlags, execute work: @escaping @convention(block) () -> Void)
21 | }
22 |
23 | extension DispatchQueue: DispatchQueueProtocol {
24 |
25 | /// Convenience version of `DispatchQueue/async(group:qos:flags:execute:)` method.
26 | /// SeeAlso: ``DispatchQueueProtocol/async(flags:execute:)``
27 | func async(flags: DispatchWorkItemFlags = [], execute work: @escaping @convention(block) () -> Void) {
28 | async(group: nil, qos: .unspecified, flags: flags, execute: work)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Framework/Source Files/Utilities/SynchronizedArray.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2023 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 |
8 | /// A thread-safe `Array`, which synchronizes access to its elements.
9 | /// To avoid race condition while performing multiple operations on ``SynchronizedArray`` use ``SynchronizedArray/read(_:)`` and ``SynchronizedArray/write(_:)`` methods.
10 | final class SynchronizedArray {
11 |
12 | /// A queue which manages access to ``SynchronizedArray/storage``. Should be a concurrent queue.
13 | private let queue: DispatchQueueProtocol
14 | /// Internal storage of the ``SynchronizedArray``.
15 | private var storage: [Element]
16 |
17 | /// Initializes new ``SynchronizedArray`` with empty storage.
18 | /// - Parameter queue: A queue which manages access to internal storage. Concurrent queue is recommended.
19 | init(
20 | queue: DispatchQueueProtocol = DispatchQueue(label: "co.netguru.lib.blueswift.SynchronizedArray", attributes: .concurrent)
21 | ) {
22 | self.queue = queue
23 | storage = []
24 | }
25 |
26 | /// Initializes new ``SynchronizedArray`` with containing provided elements.
27 | convenience init(contentsOf array: [Element]) {
28 | self.init()
29 | storage = array
30 | }
31 |
32 | /// Allows accessing element by its index.
33 | subscript(_ index: Int) -> Element {
34 | get {
35 | read {
36 | $0[index]
37 | }
38 | }
39 | set {
40 | write {
41 | $0[index] = newValue
42 | }
43 | }
44 | }
45 | }
46 |
47 | extension SynchronizedArray {
48 |
49 | /// The number of elements in the array.
50 | var count: Int {
51 | read { $0.count }
52 | }
53 |
54 | /// A Boolean value indicating whether the collection is empty.
55 | var isEmpty: Bool {
56 | read { $0.isEmpty }
57 | }
58 |
59 | /// The first element of the array.
60 | var first: Element? {
61 | read { $0.first }
62 | }
63 |
64 | /// The last element of the collection.
65 | var last: Element? {
66 | read { $0.last }
67 | }
68 |
69 | /// Adds a new element at the end of the array.
70 | /// - Parameter newElement: The element to append to the array.
71 | func append(_ newElement: Element) {
72 | write {
73 | $0.append(newElement)
74 | }
75 | }
76 |
77 | /// Adds a new element at the end of the array.
78 | /// - Parameter newElement: The element to append to the array.
79 | func append(contentsOf newElements: S) where Element == S.Element, S : Sequence {
80 | write {
81 | $0.append(contentsOf: newElements)
82 | }
83 | }
84 |
85 | /// Removes and returns the element at the specified position.
86 | /// - Parameter index: The position of the element to remove. index must be a valid index of the array.
87 | func remove(at index: Int) {
88 | write {
89 | $0.remove(at: index)
90 | }
91 | }
92 |
93 | /// Removes all the elements that satisfy the given predicate.
94 | /// - Parameter shouldBeRemoved: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element should be removed from the array.
95 | func removeAll(where shouldBeRemoved: @escaping (Element) -> Bool) {
96 | write {
97 | $0.removeAll(where: shouldBeRemoved)
98 | }
99 | }
100 |
101 | /// Removes the element at the specified position, checking if provided position is a valid index of the array.
102 | /// - Parameter index: The position of the element to remove.
103 | func safelyRemove(at index: Int) {
104 | write {
105 | guard index < $0.count, index >= 0 else { return }
106 | $0.remove(at: index)
107 | }
108 | }
109 |
110 | /// Returns the first element of the sequence that satisfies the given predicate.
111 | /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match.
112 | /// - Returns: an element from the array.
113 | func first(where predicate: (Element) -> Bool) -> Element? {
114 | read {
115 | $0.first(where: predicate)
116 | }
117 | }
118 | }
119 |
120 | extension SynchronizedArray {
121 |
122 | /// Use `read` method to execute multiple read-operations on the data.
123 | /// - Parameter block: block of code which takes the storage (`[Element]`) as argument and returns a value.
124 | /// - Returns: result of block of code.
125 | func read(_ block: ([Element]) throws -> T) rethrows -> T {
126 | try queue.sync {
127 | try block(storage)
128 | }
129 | }
130 | /// Use `write` method to execute multiple write-operations on the data.
131 | /// - Parameter block: block of code which takes the storage (`[Element]`) as argument and allows to mutate it.
132 | func write(_ block: @escaping (inout [Element]) -> Void) {
133 | queue.async(flags: .barrier) {
134 | block(&self.storage)
135 | }
136 | }
137 | }
138 |
139 | extension SynchronizedArray: ExpressibleByArrayLiteral {
140 |
141 | /// SeeAlso: ``ExpressibleByArrayLiteral/init(arrayLiteral:)``.
142 | convenience init(arrayLiteral elements: Element...) {
143 | self.init(contentsOf: elements)
144 | }
145 | }
146 |
147 | extension SynchronizedArray: Equatable where Element: Equatable {
148 |
149 | /// SeeAlso: `Equatable/==(lhs:rhs:)`.
150 | static func == (lhs: SynchronizedArray, rhs: SynchronizedArray) -> Bool {
151 | lhs.storage == rhs.storage
152 | }
153 | }
154 |
155 | extension SynchronizedArray: CustomDebugStringConvertible {
156 |
157 | /// SeeAlso: `CustomDebugStringConvertible/debugDescription`.
158 | var debugDescription: String {
159 | storage.debugDescription
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/Framework/Supporting Files/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | $(PRODUCT_BUNDLE_VERSION_STRING)
19 | CFBundleVersion
20 | $(PRODUCT_BUNDLE_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Netguru
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Title
2 |
3 |
4 | ### Motivation
5 |
6 |
7 | ### Task Description
8 |
9 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.4
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "BlueSwift",
6 | platforms: [ .iOS(.v10) ],
7 | products: [
8 | .library(
9 | name: "BlueSwift",
10 | targets: ["BlueSwift"]),
11 | ],
12 | targets: [
13 | .target(
14 | name: "BlueSwift",
15 | path: "Framework/Source Files"
16 | ),
17 | .testTarget(name: "BlueSwiftTests",
18 | dependencies: ["BlueSwift"],
19 | path: "Unit Tests"
20 | ),
21 | ]
22 | )
23 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 |
5 | 
6 | 
7 | 
8 | 
9 | 
10 |
11 | Easy to use Bluetooth open source library brought to you by Netguru.
12 | 🤟 Probably the easiest way to interact with bluetooth peripherals 🤟
13 |
14 | ## 🤹🏻♂️ Features
15 |
16 | - [x] Handles connection with remote peripherals.
17 | - [x] Handles advertising an iPhone as Bluetooth LE peripheral.
18 | - [x] Closure based read/write/notify requests.
19 | - [x] Built in data conversion method with `Command` wrapper.
20 |
21 | ## 📲 Connection:
22 |
23 | Below you can find an easy code sample to connect to the peripheral.
24 | Really thats all that is needed 🍾🍾
25 |
26 | ```swift
27 | let connection = BluetoothConnection.shared
28 | let characteristic = try! Characteristic(uuid: "your_characteristic_uuid", shouldObserveNotification: true)
29 | let service = try! Service(uuid: "your_service_uuid", characteristics: [characteristic])
30 | let configuration = try! Configuration(services: [service], advertisement: "your_advertising_uuid")
31 | let peripheral = Peripheral(configuration: configuration)
32 | connection.connect(peripheral) { error in
33 | // do awesome stuff
34 | }
35 | ```
36 |
37 | ## 📡 Advertisement:
38 |
39 | Below you can find a code sample the setup the iPhone to advertise Bluetooth.
40 | That's all it takes to advertise one service containing one characteristic.
41 |
42 | ```swift
43 | let characteristic = try! Characteristic(uuid: "your_characteristic_uuid")
44 | let service = try! Service(uuid: "your_service_uuid", characteristics: [characteristic])
45 | let configuration = try! Configuration(services: [service], advertisement: "your_service_uuid")
46 | let peripheral = Peripheral(configuration: configuration, advertisementData: [.localName("Test"), .servicesUUIDs("your_service_uuid")])
47 | advertisement.advertise(peripheral: peripheral) { error in
48 | // oh no, something failed in that case
49 | }
50 | ```
51 |
52 | ## 📟 📲 Data transfer:
53 |
54 | Of course data transfer is also possible, both for advertising and connection mode!
55 | Below there are some basic examples, for more please see `More usage` section 👇🏻
56 |
57 | ## Connection mode:
58 |
59 | ```swift
60 | let command = Command.utf8String("Hello world")
61 | peripheral.write(command: command, characteristic: someCharacteristic, handler: { error in
62 | // written!
63 | })
64 | peripheral.read(characteristic, handler: { data, error in
65 | // read!
66 | })
67 | ```
68 |
69 | ## Advertisement mode:
70 |
71 | ```swift
72 | let command = Command.int8(3)
73 | advertisement.update(command, characteristic: characteristic) { error in
74 | // data updated!
75 | }
76 | advertisement.writeRequestCallback = { characteristic, data in
77 | // written!
78 | }
79 | ```
80 |
81 | ## ⚙️ More usage:
82 |
83 | For more advanced usage check out documentation page at: https://netguru.github.io/BlueSwift/.
84 | Also feel free to check example project bundled with this repository! 👩🏼🏫 👨🏼🏫
85 | It's a complete app that allows connection and sending text messages between two iPhones.
86 |
87 | ## 🛠 Dependency management:
88 |
89 | BlueSwift can be drag'n dropped to the project directory,
90 | but what's more important it's supported by most common dependency management!
91 |
92 | ### 
93 |
94 | Just drop the line below to your Podfile:
95 |
96 | `pod 'BlueSwift'`
97 |
98 | (but probably you'd like to pin it to the nearest major release, so `pod 'BlueSwift' , '~> 1.1.6'`)
99 |
100 | ### 
101 |
102 | The same as with Cocoapods, insert the line below to your Cartfile:
103 |
104 | `github 'netguru/BlueSwift'`
105 |
106 | , or including version - `github 'netguru/BlueSwift' ~> 1.1.6`
107 |
108 | ## 📄 License
109 |
110 | (As all cool open source software, it's...)
111 | Licensed under MIT license.
112 |
113 | Also it would be really nice if you could drop us a line about your usage!! 🚀🚀
114 |
--------------------------------------------------------------------------------
/Unit Tests/CommandTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import XCTest
7 | @testable import BlueSwift
8 |
9 | class CommandTests: XCTestCase {
10 |
11 | func testHexDataConversion() {
12 | let properShortString = "1A2B"
13 | let properLongString = "0123456789ABCDEF"
14 | let improperString = "12345Z"
15 |
16 | let shortData = try! properShortString.hexDecodedData()
17 | let shortFirst = shortData.subdata(in: 0..<1)
18 | XCTAssertEqual(shortFirst.hexEncodedString, "1A", "Incorrect hex string parse")
19 | let shortSecond = shortData.subdata(in: 1..<2)
20 | XCTAssertEqual(shortSecond.hexEncodedString, "2B", "Incorrect hex string parse")
21 | let longData = try! properLongString.hexDecodedData()
22 | let longFirst = longData.subdata(in: 0..<1)
23 | XCTAssertEqual(longFirst.hexEncodedString, "01", "Incorrect hex string parse")
24 | let longSecond = longData.subdata(in: 3..<4)
25 | XCTAssertEqual(longSecond.hexEncodedString, "67", "Incorrect hex string parse")
26 | let longLast = longData.subdata(in: 7..<8)
27 | XCTAssertEqual(longLast.hexEncodedString, "EF", "Incorrect hex string parse")
28 |
29 | do {
30 | _ = try improperString.hexDecodedData()
31 | XCTFail("Improper string successfull parse should throw an error.")
32 | }
33 | catch let error {
34 | guard let error = error as? Command.ConversionError else {
35 | XCTFail()
36 | return
37 | }
38 | XCTAssertEqual(error, Command.ConversionError.incorrectInputFormat, "Incorrect error created on wrong hex string parse")
39 | }
40 | }
41 |
42 | func testIntegerDataConversion() {
43 |
44 | let eightInt: UInt8 = 3
45 | let sixteenInt: UInt16 = 200
46 | let thirtyTwoInt: UInt32 = 65637
47 |
48 | let eightData = eightInt.decodedData
49 | XCTAssertEqual(eightData.count, 1, "Incorrect length for 8 bit data.")
50 | let sixteenData = sixteenInt.decodedData
51 | XCTAssertEqual(sixteenData.count, 2, "Incorrect length for 16 bit data.")
52 | let thirtyTwoData = thirtyTwoInt.decodedData
53 | XCTAssertEqual(thirtyTwoData.count, 4, "Incorrect length for 32 bit data.")
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Unit Tests/ConfigurationTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import XCTest
7 | import CoreBluetooth.CBUUID
8 | @testable import BlueSwift
9 |
10 | class ConfigurationTests: XCTestCase {
11 |
12 | func testServiceUUIDCreation() {
13 |
14 | let correctUUIDLong = "4983BB4D-EC7A-47DD-88E7-EB48859B083A"
15 | let correctUUIDShort = "1800"
16 | let incorrectUUID = "test"
17 |
18 | let serviceLong = try? Service(uuid: correctUUIDLong, characteristics: [])
19 | let serviceShort = try? Service(uuid: correctUUIDShort, characteristics: [])
20 |
21 | XCTAssertEqual(serviceLong?.bluetoothUUID.uuidString, correctUUIDLong, "Incorrect parse of long UUID's for Service.")
22 | XCTAssertEqual(serviceShort?.bluetoothUUID.uuidString, correctUUIDShort, "Incorrect parse of short UUID's for Service.")
23 |
24 | do {
25 | _ = try Service(uuid: incorrectUUID, characteristics: [])
26 | XCTFail("Improper Service creation should throw an error.")
27 | } catch let error {
28 | guard let error = error as? CBUUID.CreationError else {
29 | XCTFail()
30 | return
31 | }
32 | XCTAssertEqual(error, CBUUID.CreationError.invalidString, "Incorrect error returned when parsing wrong UUID.")
33 | }
34 | }
35 |
36 | func testCharacteristicUUIDCreation() {
37 |
38 | let correctUUIDLong = "9CD82657-17AC-404D-B9D6-CF7D4F0F94A2"
39 | let correctUUIDShort = "2A01"
40 | let incorrectUUID = "2A01B"
41 |
42 | let characteristicLong = try? Characteristic(uuid: correctUUIDLong)
43 | let characteristicShort = try? Characteristic(uuid: correctUUIDShort)
44 |
45 | XCTAssertEqual(characteristicLong?.bluetoothUUID.uuidString, correctUUIDLong, "Incorrect parse of long UUID's for Characteristic.")
46 | XCTAssertEqual(characteristicShort?.bluetoothUUID.uuidString, correctUUIDShort, "Incorrect parse of short UUID's for Characteristic.")
47 |
48 | do {
49 | _ = try Characteristic(uuid: incorrectUUID)
50 | XCTFail("Improper Characteristic creation should throw an error.")
51 | } catch let error {
52 | guard let error = error as? CBUUID.CreationError else {
53 | XCTFail()
54 | return
55 | }
56 | XCTAssertEqual(error, CBUUID.CreationError.invalidString, "Incorrect error returned when parsing wrong UUID.")
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Unit Tests/ExtensionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import XCTest
7 | import CoreBluetooth
8 | @testable import BlueSwift
9 |
10 | class ExtensionTests: XCTestCase {
11 |
12 | //MARK: String extension tests
13 |
14 | func testShortUUIDValidation() {
15 | let fourCharactersCorrect = "1800"
16 | let sixCharactersCorrext = "2A0B16"
17 |
18 | let tooLong = "1800000"
19 | let tooShort = "67A"
20 |
21 | let notHexadecimal = "180X"
22 |
23 | XCTAssertTrue(fourCharactersCorrect.isValidShortenedUUID(), "Four character valid uuid validation failed")
24 | XCTAssertTrue(sixCharactersCorrext.isValidShortenedUUID(), "Six character valid uuid validation failed")
25 | XCTAssertFalse(tooLong.isValidShortenedUUID(), "Too long invalid uuid validation passed")
26 | XCTAssertFalse(tooShort.isValidShortenedUUID(), "Too short invalid uuid validation passed")
27 | XCTAssertFalse(notHexadecimal.isValidShortenedUUID(), "Not hexadecimal invalid uuid validation passed")
28 | }
29 |
30 | //MARK: Array extension tests
31 |
32 | func testArrayCommonPart() {
33 | let first = [1, 2, 3, 4, 5]
34 | let second = [4, 5, 6, 7]
35 |
36 | let common = first.matchingElementsWith(second) { $0 == $1 }
37 |
38 | XCTAssertEqual(common.count, 2, "Expected matches count is invalid")
39 | XCTAssertEqual(common.first?.0, 4, "Array first element matching failed")
40 | XCTAssertEqual(common.last?.1, 5, "Array second element matching failed")
41 | }
42 |
43 | func testServiceMatching() {
44 | let services = [try! Service(uuid: "1800", characteristics: []),
45 | try! Service(uuid: "1801", characteristics: [])]
46 | let cbServices = [CBMutableService(type: CBUUID(string: "1800"), primary: false) as CBService,
47 | CBMutableService(type: CBUUID(string: "1802"), primary: false) as CBService]
48 |
49 | let common = services.matchingElementsWith(cbServices)
50 |
51 | XCTAssertEqual(common.count, 1, "Expected matches count is invalid")
52 | XCTAssertEqual(common.first?.0.bluetoothUUID.uuidString, common.first?.1.uuid.uuidString, "Expected uuid's does not match")
53 | }
54 |
55 | func testCharacteristicMatching() {
56 | let services = [try! Characteristic(uuid: "2A0B"),
57 | try! Characteristic(uuid: "1801")]
58 | let cbCharacteristics = [CBMutableCharacteristic(type: CBUUID(string: "1801"), properties: [.read], value: nil, permissions: .readable) as CBCharacteristic,
59 | CBMutableCharacteristic(type: CBUUID(string: "1800"), properties: [.read], value: nil, permissions: .readable) as CBCharacteristic]
60 |
61 | let common = services.matchingElementsWith(cbCharacteristics)
62 |
63 | XCTAssertEqual(common.count, 1, "Expected matches count is invalid")
64 | XCTAssertEqual(common.first?.0.bluetoothUUID.uuidString, common.first?.1.uuid.uuidString, "Expected uuid's does not match")
65 | }
66 |
67 | func testAdvertisementCombine() {
68 |
69 | let advertisementParameters: [AdvertisementData] = [.localName("Test"), .servicesUUIDs("2A01"), .servicesUUIDs("2A02"), .txPower(30)]
70 | let dictionary = advertisementParameters.combined()
71 |
72 | XCTAssertEqual(dictionary[CBAdvertisementDataLocalNameKey] as? String, "Test")
73 | XCTAssertEqual(dictionary[CBAdvertisementDataTxPowerLevelKey] as? Int, 30)
74 | guard let array = dictionary[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] else {
75 | XCTFail()
76 | return
77 | }
78 | XCTAssertEqual(array[0].uuidString, "2A01")
79 | XCTAssertEqual(array[1].uuidString, "2A02")
80 | }
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/Unit Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
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 | $(PRODUCT_BUNDLE_VERSION_STRING)
19 | CFBundleVersion
20 | $(PRODUCT_BUNDLE_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Unit Tests/Mocks/MockDispatchQueue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import Foundation
7 | @testable import BlueSwift
8 |
9 | class MockDispatchQueue: DispatchQueueProtocol {
10 |
11 | func sync(execute work: () throws -> T) rethrows -> T {
12 | try work()
13 | }
14 |
15 | func async(flags: DispatchWorkItemFlags, execute work: @escaping @convention(block) () -> Void) {
16 | work()
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Unit Tests/SequenceExtensionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SequenceExtensionTests.swift
3 | // Bluetooth
4 | //
5 | // Created by Filip Zieliński on 08/04/2022.
6 | // Copyright © 2022 Netguru. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import CoreBluetooth
11 | @testable import BlueSwift
12 |
13 | final class SequenceExtensionTests: XCTestCase {
14 |
15 | private let fixtureReference = SimpleClass()
16 | private let fixtureOptionalReference = SimpleClass()
17 | private lazy var fixtureTestClass = TestClass(someReference: fixtureReference, someOptionalReference: fixtureOptionalReference)
18 |
19 | func testFirstSequenceElement_withIdenticalProperty() {
20 | // given:
21 | var sut: [TestClass] = [
22 | TestClass(someReference: .init(), someOptionalReference: nil),
23 | TestClass(someReference: .init(), someOptionalReference: .init()),
24 | fixtureTestClass,
25 | TestClass(someReference: .init(), someOptionalReference: .init()),
26 | ]
27 |
28 | // then:
29 | testFindingElement_withMatchingIdenticalProperty(sut: sut)
30 |
31 | // given
32 | sut = [
33 | fixtureTestClass,
34 | TestClass(someReference: .init(), someOptionalReference: .init()),
35 | TestClass(someReference: .init(), someOptionalReference: .init()),
36 | TestClass(someReference: .init(), someOptionalReference: nil)
37 | ]
38 |
39 | // then:
40 | testFindingElement_withMatchingIdenticalProperty(sut: sut)
41 |
42 | // given
43 | sut = [
44 | TestClass(someReference: .init(), someOptionalReference: .init()),
45 | TestClass(someReference: .init(), someOptionalReference: nil),
46 | TestClass(someReference: .init(), someOptionalReference: .init()),
47 | fixtureTestClass
48 | ]
49 |
50 | // then:
51 | testFindingElement_withMatchingIdenticalProperty(sut: sut)
52 | }
53 | }
54 |
55 | private extension SequenceExtensionTests {
56 |
57 | func testFindingElement_withMatchingIdenticalProperty(sut: [TestClass]) {
58 | XCTAssert(sut.first(where: \.someReference, isIdenticalTo: fixtureReference) === fixtureTestClass, "Should find object with matching identical (`===`) property")
59 | XCTAssert(sut.first(where: \.someOptionalReference, isIdenticalTo: fixtureOptionalReference) === fixtureTestClass, "Should find object with matching identical (`===`) property")
60 | XCTAssertNil(sut.first(where: \.someReference, isIdenticalTo: fixtureOptionalReference), "Should not find any object with matching identical (`===`) property")
61 | XCTAssertNil(sut.first(where: \.someOptionalReference, isIdenticalTo: fixtureReference), "Should not find any object with matching identical (`===`) property")
62 | XCTAssertNil(sut.first(where: \.someReference, isIdenticalTo: .init()), "Should not find any object with matching identical (`===`) property")
63 | XCTAssertNil(sut.first(where: \.someOptionalReference, isIdenticalTo: .init()), "Should not find any object with matching identical (`===`) property")
64 | }
65 | }
66 |
67 | private final class TestClass: Identifiable {
68 |
69 | let someReference: SimpleClass
70 | let someOptionalReference: SimpleClass?
71 |
72 | init(someReference: SimpleClass, someOptionalReference: SimpleClass?) {
73 | self.someReference = someReference
74 | self.someOptionalReference = someOptionalReference
75 | }
76 | }
77 |
78 | private final class SimpleClass {}
79 |
--------------------------------------------------------------------------------
/Unit Tests/SynchronizedArrayTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2018 Netguru Sp. z o.o. All rights reserved.
3 | // Licensed under the MIT License.
4 | //
5 |
6 | import XCTest
7 | import CoreBluetooth.CBUUID
8 | @testable import BlueSwift
9 |
10 | class SynchronizedArrayTests: XCTestCase {
11 |
12 | func testInitializers() {
13 | // given
14 | var sut = SynchronizedArray()
15 |
16 | // then
17 | XCTAssertTrue(sut.isEmpty)
18 | XCTAssertEqual(sut.count, 0)
19 |
20 | // given
21 | sut = .init(arrayLiteral: 1, 2, 3)
22 |
23 | // then
24 | XCTAssertEqual(sut.count, 3)
25 | XCTAssertEqual(sut[0], 1)
26 | XCTAssertEqual(sut[1], 2)
27 | XCTAssertEqual(sut[2], 3)
28 |
29 | // given
30 | sut = .init(contentsOf: [3, 4])
31 |
32 | // then
33 | XCTAssertEqual(sut.count, 2)
34 | XCTAssertEqual(sut[0], 3)
35 | XCTAssertEqual(sut[1], 4)
36 | }
37 |
38 | func testSubscript() {
39 | // given
40 | let queue = MockDispatchQueue()
41 | let sut: SynchronizedArray = .init(queue: queue)
42 | sut.append(contentsOf: [1, 2, 3])
43 |
44 | // then
45 | XCTAssertEqual(sut[0], 1)
46 | XCTAssertEqual(sut[1], 2)
47 | XCTAssertEqual(sut[2], 3)
48 |
49 | // when
50 | sut[0] = 4
51 | sut[1] = 5
52 | sut[2] = 6
53 |
54 | // then
55 | XCTAssertEqual(sut, [4, 5, 6])
56 | }
57 |
58 | func testFirstAndLast() {
59 | // given
60 | var sut: SynchronizedArray = []
61 |
62 | // then
63 | XCTAssertNil(sut.first)
64 | XCTAssertNil(sut.last)
65 |
66 | // given
67 | sut = [2]
68 |
69 | // then
70 | XCTAssertEqual(sut.first, 2)
71 | XCTAssertEqual(sut.last, 2)
72 |
73 | // given
74 | sut = [1, 2, 3]
75 |
76 | // then
77 | XCTAssertEqual(sut.first, 1)
78 | XCTAssertEqual(sut.last, 3)
79 |
80 | // when
81 | let element = sut.first(where: { $0 == 2 })
82 |
83 | // then
84 | XCTAssertEqual(element, 2)
85 | }
86 |
87 | func testEquality() {
88 | // given
89 | var sut1: SynchronizedArray = []
90 | var sut2: SynchronizedArray = []
91 |
92 | // then
93 | XCTAssertEqual(sut1, sut2)
94 |
95 | // given
96 | sut1 = ["string"]
97 | sut2 = []
98 |
99 | // then
100 | XCTAssertNotEqual(sut1, sut2)
101 |
102 | // given
103 | sut1 = ["fixture", "string"]
104 | sut2 = ["fixture", "string"]
105 |
106 | // then
107 | XCTAssertEqual(sut1, sut2)
108 | }
109 |
110 | func testAppend() {
111 | // given
112 | let queue = MockDispatchQueue()
113 | let sut: SynchronizedArray = .init(queue: queue)
114 |
115 | // when
116 | sut.append(1)
117 |
118 | // then
119 | XCTAssert(sut == [1])
120 |
121 | // when
122 | sut.append(2)
123 |
124 | // then
125 | XCTAssertEqual(sut, [1, 2])
126 |
127 | // when
128 | sut.append(contentsOf: [3, 4, 5])
129 |
130 | // then
131 | XCTAssertEqual(sut, [1, 2, 3, 4, 5])
132 | }
133 |
134 | func testRemove() {
135 | // given
136 | let queue = MockDispatchQueue()
137 | let sut: SynchronizedArray = .init(queue: queue)
138 |
139 | // when
140 | sut.removeAll(where: { _ in true })
141 |
142 | // then
143 | XCTAssert(sut.isEmpty)
144 |
145 | // given
146 | sut.append(contentsOf: [1, 2, 3])
147 |
148 | // when
149 | sut.removeAll(where: { _ in false })
150 |
151 | // then
152 | XCTAssertEqual(sut, [1, 2, 3])
153 |
154 | // when
155 | sut.removeAll(where: { $0 == 2 })
156 |
157 | // then
158 | XCTAssertEqual(sut, [1, 3])
159 |
160 | // when
161 | sut.removeAll(where: { _ in true })
162 |
163 | // then
164 | XCTAssertEqual(sut, [])
165 |
166 | // given
167 | sut.append(contentsOf: [1, 2, 3, 4, 5])
168 |
169 | // when
170 | sut.safelyRemove(at: 5)
171 |
172 | // then
173 | XCTAssertEqual(sut, [1, 2, 3, 4, 5])
174 |
175 | // when
176 | sut.safelyRemove(at: 4)
177 |
178 | // then
179 | XCTAssertEqual(sut, [1, 2, 3, 4])
180 |
181 | // when
182 | sut.remove(at: 3)
183 |
184 | // then
185 | XCTAssertEqual(sut, [1, 2, 3])
186 |
187 | // when
188 | sut.remove(at: 0)
189 |
190 | // then
191 | XCTAssertEqual(sut, [2, 3])
192 | }
193 |
194 | func testConcurrentAppend() {
195 | let sut = SynchronizedArray()
196 | let iterations = 1000
197 |
198 | // Regular Swift `Array` would crash here (`EXC_BAD_ACCESS`):
199 | DispatchQueue.concurrentPerform(iterations: iterations) { index in
200 | sut.append(index)
201 | }
202 |
203 | XCTAssertEqual(sut.count, iterations)
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/bitrise.yml:
--------------------------------------------------------------------------------
1 | #
2 | # bitrise.yml
3 | # Copyright © 2017 Netguru Sp. z o.o. All rights reserved.
4 | #
5 | # This `bitrise.yml` variant contains the default configuration to be used in
6 | # an iOS app repository. You can strip the comments in your `bitrise.yml` if
7 | # you want.
8 | #
9 | # Scripts in the following `bitrise.yml` file make use of the following
10 | # environment variables that should be set up as secret:
11 | #
12 | # Also, those scripts use the following environment variables that are declared
13 | # publicly in the following file:
14 | #
15 | # - `XCODEBUILD_PROJECT`: A path to a project or a workspace Xcode file.
16 | #
17 | # - `XCODEBUILD_SCHEME`: A name of the scheme to be built.
18 | #
19 | # - `XCODEBUILD_OPTIONS`: An additional build settings passed straight to each
20 | # `xcodebuild` invocation.
21 | #
22 |
23 | # CLI metadata.
24 | #
25 | # This metadata is required to always be in `bitrise.yml`. The `format_version`
26 | # setting will be occasionally bumped.
27 |
28 | format_version: 1.3.1
29 | default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
30 |
31 | # Workflow trigger map.
32 | #
33 | # The following trigger map triggers a build only for pull requests against
34 | # `develop` and `master` branches (from forks as well) and pushes to `develop`
35 | # and `master` branches.
36 | #
37 | # More on trigger maps: http://devcenter.bitrise.io/webhooks/trigger-map
38 |
39 | trigger_map:
40 |
41 | - push_branch: develop
42 | workflow: build-pull-request
43 |
44 | - push_branch: master
45 | workflow: build-pull-request
46 |
47 | - pull_request_target_branch: develop
48 | workflow: build-pull-request
49 |
50 | - pull_request_target_branch: master
51 | workflow: build-pull-request
52 |
53 | # Environment configuration.
54 | #
55 | # This list contains the default environment variables shared between workflows.
56 |
57 | app:
58 | envs:
59 | - XCODEBUILD_PROJECT: ./Bluetooth.xcodeproj
60 | - XCODEBUILD_OPTIONS: _BUILD_NUMBER=$BITRISE_BUILD_NUMBER
61 |
62 | # Workflow declarations.
63 | #
64 | # This list contains workflows used in the above trigger map.
65 |
66 | workflows:
67 |
68 | build-pull-request:
69 | envs:
70 | - XCODEBUILD_SCHEME: BlueSwift-iOS
71 | before_run:
72 | - test-xcode
73 |
74 | # Bootstrap code signing by installing certificates and provisioning profiles.
75 |
76 | bootstrap-code-signing:
77 | steps:
78 | - certificate-and-profile-installer: {}
79 |
80 | # Build and test an app using `xcodebuild` command.
81 |
82 | test-xcode:
83 | steps:
84 | - xcode-test:
85 | inputs:
86 | - project_path: $XCODEBUILD_PROJECT
87 | - scheme: $XCODEBUILD_SCHEME
88 | - xcodebuild_test_options: $XCODEBUILD_OPTIONS
89 | - simulator_device: iPhone 12
90 |
91 | # Deploy build artifacts to bitrise.io.
92 |
93 | deploy-bitriseio:
94 | steps:
95 | - deploy-to-bitrise-io:
96 | inputs:
97 | - notify_user_groups: none
98 | - is_enable_public_page: false
99 |
100 |
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/docs/advertisementData.md:
--------------------------------------------------------------------------------
1 | ## AdverisementData
2 |
3 | ## Overview
4 |
5 | It's a convenient enum to deal with values put in advertisement header when acting as a peripheral. Used when initializing [Peripheral](./peripheral.md) in an `Advertisable` generic mode. It's intended to be a wrapper around CoreBluetooth default statics such as `CBAdvertisementDataServiceUUIDsKey` which makes them hard to remember.
6 |
7 | ## Topics
8 |
9 | ```swift
10 | case localName(String)
11 | ```swift
12 |
13 | ```swift
14 | case servicesUUIDs(String)
15 | ```swift
16 |
17 | ```swift
18 | case serviceData(Data)
19 | ```swift
20 |
21 | ```swift
22 | case txPower(Int)
23 | ```swift
24 |
25 | ```swift
26 | case manufacturersData(String)
27 | ```swift
28 |
29 | ```swift
30 | case custom(String, Any)
31 | ```swift
32 |
--------------------------------------------------------------------------------
/docs/bluetoothAdvertisement.md:
--------------------------------------------------------------------------------
1 | ## BleutoothAdvertisement
2 |
3 | 🤖 Work in progress, please check back in a while 🤖
--------------------------------------------------------------------------------
/docs/bluetoothConnection.md:
--------------------------------------------------------------------------------
1 | ## BluetoothConnection
2 |
3 | ## Overview
4 |
5 | It's a class responsible for management of peripheral connections. It's a singleton by design as it's recommended to use only one instance of `CBCentralManager` per application. One instance of that class can be also responsible for connecting multiple peripherals. Although you're welcome to create your own instance.
6 | It uses [Peripheral](./peripheral.md) class for connection.
7 |
8 | ## Topics
9 |
10 | ```swift
11 | static let shared = BluetoothConnection()
12 | ```
13 |
14 | A singleton instance of the class. Motivation to use it is carried by the fact that it's recommended to use only one instance of `CBCentralManager` per application.
15 |
16 | ```swift
17 | var advertisementValidationHandler: ((Peripheral, String, [String: Any]) -> (Bool))?
18 | ```
19 |
20 | An optional closure, nil by default. If set you can setup custom filtering of scanned peripherals basing on their advertising packets.
21 | Advertising is passed in `[String: Any]` dictionary. This data is sent along with a string representing a CoreBluetooth identifier of the device(unique and not changing per each device) and corresponding peripheral instance.
22 | If false is returned from this method, no attempt to connect a given peripheral will we attempted.
23 |
24 | ```swift
25 | var peripheralConnectionCancelledHandler: ((Peripheral, CBPeripheral) -> Void)?
26 | ```
27 |
28 | An optional closure, nil by default - peripheral connection cancelled handler. Called when disconnecting a peripheral using `disconnect(_:)` is completed.
29 | Contains matched peripheral and native peripheral from CoreBluetooth.
30 |
31 | ```swift
32 | func connect(_ peripheral: Peripheral, handler: ((ConnectionError?) -> ())?)
33 | ```
34 |
35 | Main method responsible for connecting a peripheral. After configuring a proper [Peripheral](./peripheral.md) class it can be passed here to be connected. After it's connected or some error is raised, the `handler` closure is called. If error is nil, you can assume connection went well.
36 | Possible errors are raised when Bluetooth is unavailable or turned off, maximum connected devices limit is exceeded(the limit is 8 devices) or that device is already connected.
37 |
38 | ```swift
39 | func disconnect(_ peripheral: Peripheral) throws
40 | ```
41 |
42 | Disconnects a given device. It can throw only in case the device is not already connected so the error can be safely ignored in most cases.
43 |
--------------------------------------------------------------------------------
/docs/characteristic.md:
--------------------------------------------------------------------------------
1 | ## Characteristic
2 |
3 | ## Overview
4 |
5 |
6 |
7 | ## Topics
8 |
9 | ```swift
10 | public let uuid: String
11 | ```
12 |
13 | ```swift
14 | let isObservingValue: Bool
15 | ```
16 |
17 | ```swift
18 | var notifyHandler: ((Data?) -> ())?
19 | ```
20 |
21 | ```swift
22 | init(uuid: String, shouldObserveNotification: Bool = false) throws
23 | ```
24 |
--------------------------------------------------------------------------------
/docs/command.md:
--------------------------------------------------------------------------------
1 | ## Command
2 |
3 | 🤖 Work in progress, please check back in a while 🤖
--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------
1 | ## Configuration
2 |
3 | 🤖 Work in progress, please check back in a while 🤖
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Motivation:
2 |
3 | For the past previous years I've worked with several apps related to Bluetooth LE technology.
4 | At the start of every project I've came to a few conclusions:
5 | - CoreBluetooh delegate based API is horrible and really full of boilerplate
6 | - I had to write many small MVP projects(eg. for testing purposes) that made point 1 even more horrible
7 |
8 | That's pretty much everything that motivated me to create a library that will handle connection and data tranfer in just a couple of lines.
9 | I've also felt that it should be handled by closures instead of delegation for easier management.
10 | Below you'll find a bit more thoroughly explained examples along with small documentation piece for every public class used in this project.
11 | Let's go with connection first.
12 |
13 | # Connection:
14 |
15 | Starting with some code to be described in detail later:
16 |
17 | ```swift
18 | let connection = BluetoothConnection.shared
19 | let characteristic = try! Characteristic(uuid: "your_characteristic_uuid", shouldObserveNotification: true)
20 | let service = try! Service(uuid: "your_service_uuid", characteristics: [characteristic])
21 | let configuration = try! Configuration(services: [service], advertisement: "your_advertising_uuid")
22 | let peripheral = Peripheral(configuration: configuration)
23 | connection.connect(peripheral) { _ in
24 | // do sth
25 | }
26 | ```
27 |
28 | the idea behind above lines is to create a specification of the expected device with a configuration file.
29 | You should create an instance of `Characteristic` or `Service` classes for each existing ones expected in a connecting device.
30 | Only if all specified characteristics and services are found on device, it's connected with no error.
31 | Please note that you don't have to specify everything contained in the device, just the one's you'll use.
32 | After that a peripheral object is created with a passed configuration, it should be persisted by some instance variable as it will be used to manage connection.
33 | If you wish to ceonnect multiple devices, create multiple peripheral.
34 | The library contains a built in singleton instance of `BluetoothConnection`, you can easily use your own instance, but creating it will create a separate `CBCentralManager` instance, and according to Apple documentation, creating more than one instance is not recommended.
35 |
36 | ```swift
37 | let command = Command.utf8String("Hello world")
38 | peripheral.write(command: command, characteristic: someCharacteristic, handler: { error in
39 | // do sth
40 | })
41 | ```
42 |
43 | The library has a built in enum for simple conversions. It may sound easy but it's really convenient :)
44 | Using a simple enum you can create input data of cerrect bit size from integers, convert custom strings to hexadecimal and, if needed, pass data created on your own.
45 | This input data should be passed by `write` method on a peripheral that was previously used to connect. Also characteristic should be passed, this also should be the one used to create configuration for this peripheral instance.
46 | If nil error is returned in the closure, write request succeed.
47 |
48 | ```swift
49 | peripheral.read(characteristic, handler: { data, error in
50 | // do sth
51 | })
52 | ```
53 |
54 | Reading values from characteristics is actually pretty similar to writing them, you should grab on the `Peripheral` object instance used for connection, fetch desired characteristic(should also be the one used for creating configuration).
55 | As the result is asynchronous, it's returned by closure - if error there is nil, reading scceed and Data object can be read.
56 |
57 | ```swift
58 | characteristic.notifyHandler = { data in
59 | // do sth
60 | }
61 | ```
62 |
63 | Do you maybe remember a moment when you had to subscribe to a characteristic and wait for value updates? With `CoreBluetooth` alone this was hell, right?
64 | Here you have a convenient closure in every `Characteristic` object instance. It will be called each time a value changes there. The only prerequisite is to call:
65 | `shouldObserveNotification: true)` in your characteristic initializer, only this way a proper variable will be set after connection and observation will be visible.
66 |
67 | # Advertisement:
68 |
69 | ```swift
70 | let characteristic = try! Characteristic(uuid: "your_characteristic_uuid")
71 | let service = try! Service(uuid: "your_service_uuid", characteristics: [characteristic])
72 | let configuration = try! Configuration(services: [service], advertisement: "your_service_uuid")
73 | let peripheral = Peripheral(configuration: configuration, advertisementData: [.localName("Test"), .servicesUUIDs("your_service_uuid")])
74 | advertisement.advertise(peripheral: peripheral) { _ in
75 | // handle possible error
76 | }
77 | ```
78 |
79 | Advertisement setup is actually not that much different than connection. The main idea of creating a configuration that will specify all services and characteristics of the device is kept, only details varies.
80 |
81 | ```swift
82 | let command = Command.int8(3)
83 | advertisement.update(command, characteristic: characteristic) { error in
84 | // notified subscribed centrals
85 | }
86 | ```
87 |
88 | Above method uses `Command` enum to create a `Data` object that will be updated on a given characteristic. It wil autmoatically update the value on each central that is currently subscribed to that characteristic.
89 |
90 | ```swift
91 | advertisement.writeRequestCallback = { characteristic, data in
92 | // handle write request
93 | }
94 | ```
95 |
96 | If you wish to handle write requests from connected centrals, assign custom value to that closure, each write attempt will be called there with associated data and characteristic.
97 |
98 | ```swift
99 | advertisement.readRequestCallback = { characteristic -> Data in
100 | // respond to read request
101 | }
102 | ```
103 |
104 | If you wish to handle read requests from connected centrals, assign custom value to that closure, each write attempt will be called there with associated characteristic. You should return any data that should be responded.
105 |
106 | # Public classes documentation:
107 |
108 | [BluetoothConnection](./bluetoothConnection.md)
109 |
110 | [Service](./service.md)
111 |
112 | [Characteristic](./characteristic.md)
113 |
114 | [Configuration](./configuration.md)
115 |
116 | [Peripheral](./peripheral.md)
117 |
118 | [Command](./command.md)
119 |
120 | [BluetoothAdvertisement](./bluetoothAdvertisement.md)
121 |
122 | [AdvertisementData](./advertisementData.md)
123 |
--------------------------------------------------------------------------------
/docs/peripheral.md:
--------------------------------------------------------------------------------
1 | ## Peripheral
2 |
3 | 🤖 Work in progress, please check back in a while 🤖
--------------------------------------------------------------------------------
/docs/service.md:
--------------------------------------------------------------------------------
1 | ## Service
2 |
3 | ## Overview
4 |
5 |
6 |
7 | ## Topics
8 |
9 | ```swift
10 | let uuid: String
11 | ```
12 |
13 | ```swift
14 | let characteristics: [Characteristic]
15 | ```
16 |
17 | ```swift
18 | init(uuid: String, characteristics: [Characteristic]) throws
19 | ```
20 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/BlueSwift/dacfbd87b80ca2593ab2c402c551eea3c8a56821/logo.png
--------------------------------------------------------------------------------