Skip to content

Commit 853f09b

Browse files
committed
Only ask bluetooth permission after first scan trigger
1 parent 535ef93 commit 853f09b

File tree

6 files changed

+90
-35
lines changed

6 files changed

+90
-35
lines changed

Improv-iOS.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |spec|
22
spec.name = "Improv-iOS"
3-
spec.version = "0.0.5"
3+
spec.version = "0.0.6"
44
spec.summary = "Easily detect and connect Improv devices to WiFi networks in iOS"
55
spec.description = "This library abstracts the bluetooth scanning for Improv devices and allow you to connect them to WiFi networks"
66
spec.author = "Improv"

Improv-iOS.xcodeproj/xcuserdata/brunopantaleao.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,22 @@
33
uuid = "60E63B13-8A48-4F9F-B6DA-D9E72F8516D3"
44
type = "1"
55
version = "2.0">
6+
<Breakpoints>
7+
<BreakpointProxy
8+
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
9+
<BreakpointContent
10+
uuid = "3521D5F5-FF22-4BD9-B821-78E9BF5499C1"
11+
shouldBeEnabled = "Yes"
12+
ignoreCount = "0"
13+
continueAfterRunningActions = "No"
14+
filePath = "Improv-iOS/BluetoothManager.swift"
15+
startingColumnNumber = "9223372036854775807"
16+
endingColumnNumber = "9223372036854775807"
17+
startingLineNumber = "72"
18+
endingLineNumber = "72"
19+
landmarkName = "scan()"
20+
landmarkType = "7">
21+
</BreakpointContent>
22+
</BreakpointProxy>
23+
</Breakpoints>
624
</Bucket>

Improv-iOS/BluetoothManager.swift

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ protocol BluetoothManagerDelegate: AnyObject {
1717
func didConnect(peripheral: CBPeripheral)
1818
func didDisconnect(peripheral: CBPeripheral)
1919
func didReceiveResult(_ result: [String]?)
20+
func didUpdateIsScanning(_ isScanning: Bool)
21+
func didFailScanningBluetoothNotAvailable()
2022
}
2123

2224
protocol BluetoothManagerProtocol {
@@ -38,31 +40,45 @@ public enum BluetoothManagerError: Error {
3840

3941
final class BluetoothManager: NSObject, BluetoothManagerProtocol {
4042

41-
private var centralManager: CBCentralManager
43+
private var centralManager: CBCentralManager?
4244
weak var delegate: BluetoothManagerDelegate?
4345

4446
var state: CBManagerState {
45-
centralManager.state
47+
centralManager?.state ?? .unknown
48+
}
49+
50+
var isScanning: Bool {
51+
centralManager?.isScanning ?? false
4652
}
4753

4854
private var bluetoothGatt: CBPeripheral?
4955
private var operationQueue = [BleOperationType]()
5056
private var pendingOperation: BleOperationType?
5157

52-
override init() {
53-
self.centralManager = CBCentralManager()
54-
super.init()
55-
56-
centralManager.delegate = self
57-
}
58+
private var scanAsSoonAsPoweredOn = false
59+
private var scanObservation: NSKeyValueObservation?
5860

5961
func scan() {
60-
let scanOptions: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey: false]
61-
centralManager.scanForPeripherals(withServices: [BluetoothUUIDs.serviceProvision], options: scanOptions)
62+
if centralManager == nil {
63+
scanAsSoonAsPoweredOn = true
64+
centralManager = CBCentralManager(delegate: self, queue: .main)
65+
} else if centralManager?.state == .poweredOn {
66+
scanObservation = centralManager?.observe(\.isScanning, options: [.new]) { [weak self] (manager, change) in
67+
if let isScanning = change.newValue {
68+
self?.delegate?.didUpdateIsScanning(isScanning)
69+
}
70+
}
71+
72+
let scanOptions: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey: false]
73+
centralManager?.scanForPeripherals(withServices: [BluetoothUUIDs.serviceProvision], options: scanOptions)
74+
} else {
75+
Logger.main.info("User has not allowed bluetooth permission or bluetooth unavailable. Bluetooth state: \(String(describing: self.centralManager?.state.rawValue))")
76+
delegate?.didFailScanningBluetoothNotAvailable()
77+
}
6278
}
6379

6480
func stopScan() {
65-
centralManager.stopScan()
81+
centralManager?.stopScan()
6682
}
6783

6884
func connectToDevice(_ peripheral: CBPeripheral) {
@@ -71,7 +87,7 @@ final class BluetoothManager: NSObject, BluetoothManagerProtocol {
7187

7288
func disconnectFromDevice(_ peripheral: CBPeripheral) {
7389
bluetoothGatt = nil
74-
centralManager.cancelPeripheralConnection(peripheral)
90+
centralManager?.cancelPeripheralConnection(peripheral)
7591
}
7692

7793
func identifyDevice() -> BluetoothManagerError? {
@@ -138,7 +154,7 @@ final class BluetoothManager: NSObject, BluetoothManagerProtocol {
138154

139155
switch operation {
140156
case let connect as Connect:
141-
centralManager.connect(connect.device, options: nil)
157+
centralManager?.connect(connect.device, options: nil)
142158
case let write as CharacteristicWrite:
143159
bluetoothGatt?.writeValue(write.data, for: write.char, type: .withResponse)
144160
case let read as CharacteristicRead:
@@ -180,6 +196,10 @@ final class BluetoothManager: NSObject, BluetoothManagerProtocol {
180196

181197
extension BluetoothManager: CBCentralManagerDelegate {
182198
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
199+
if central.state == .poweredOn, scanAsSoonAsPoweredOn {
200+
scanAsSoonAsPoweredOn = false
201+
scan()
202+
}
183203
delegate?.didUpdateBluetoohState(central.state)
184204
}
185205

Improv-iOS/ImprovManager.swift

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,28 @@ public protocol ImprovManagerProtocol: ObservableObject {
2424

2525
public protocol ImprovManagerDelegate: AnyObject {
2626
func didUpdateBluetoohState(_ state: CBManagerState)
27-
2827
func didUpdateFoundDevices(devices: [String : CBPeripheral])
29-
3028
func didConnect(peripheral: CBPeripheral)
31-
3229
func didDisconnect(peripheral: CBPeripheral)
33-
3430
func didUpdateDeviceState(_ state: DeviceState?)
35-
3631
func didUpdateErrorState(_ state: ErrorState?)
37-
3832
func didReceiveResult(_ result: [String]?)
39-
4033
func didReset()
34+
func didUpdateIsScanning(_ isScanning: Bool)
35+
func didFailScanningBluetoothNotAvailable()
36+
}
37+
38+
public extension ImprovManagerDelegate {
39+
func didUpdateBluetoohState(_ state: CBManagerState) {}
40+
func didUpdateFoundDevices(devices: [String : CBPeripheral]) {}
41+
func didConnect(peripheral: CBPeripheral) {}
42+
func didDisconnect(peripheral: CBPeripheral) {}
43+
func didUpdateDeviceState(_ state: DeviceState?) {}
44+
func didUpdateErrorState(_ state: ErrorState?) {}
45+
func didReceiveResult(_ result: [String]?) {}
46+
func didReset() {}
47+
func didUpdateIsScanning(_ isScanning: Bool) {}
48+
func didFailScanningBluetoothNotAvailable() {}
4149
}
4250

4351
public final class ImprovManager: NSObject, ImprovManagerProtocol {
@@ -76,16 +84,11 @@ public final class ImprovManager: NSObject, ImprovManagerProtocol {
7684
}
7785

7886
public func scan() {
79-
bluetoothState = bluetoothManager.state
80-
if bluetoothState == .poweredOn {
81-
bluetoothManager.scan()
82-
scanInProgress = true
83-
}
87+
bluetoothManager.scan()
8488
}
8589

8690
public func stopScan() {
8791
bluetoothManager.stopScan()
88-
scanInProgress = false
8992
foundDevices = [:]
9093
delegate?.didUpdateFoundDevices(devices: foundDevices)
9194
}
@@ -155,4 +158,14 @@ extension ImprovManager: BluetoothManagerDelegate {
155158
lastResult = result
156159
delegate?.didReceiveResult(result)
157160
}
161+
162+
func didUpdateIsScanning(_ isScanning: Bool) {
163+
scanInProgress = isScanning
164+
delegate?.didUpdateIsScanning(isScanning)
165+
}
166+
167+
func didFailScanningBluetoothNotAvailable() {
168+
delegate?.didFailScanningBluetoothNotAvailable()
169+
}
170+
158171
}

Improv-iOSTests/ImprovManagerTests.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,11 @@ class ImprovManagerTests: XCTestCase {
2424
)
2525
}
2626

27-
func test_scan_whileBluetoothOff_doesNothing() {
28-
mockBluetoothManager.state = .poweredOff
29-
sut.scan()
30-
31-
XCTAssertFalse(mockBluetoothManager.scanCalled)
32-
}
33-
3427
func test_scan_whileBluetoothOn_callsScan() {
3528
mockBluetoothManager.state = .poweredOn
3629
sut.scan()
3730

3831
XCTAssertTrue(mockBluetoothManager.scanCalled)
39-
XCTAssertTrue(sut.scanInProgress)
4032
}
4133

4234
func test_stopScan_callsStopScan() {

Improv-iOSTests/Mocks/MockBluetoothManagerDelegate.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ final class MockBluetoothManagerDelegate: BluetoothManagerDelegate {
1717
var didConnectCalled = false
1818
var didDisconnectCalled = false
1919
var didReceiveResultCalled = false
20+
var didUpdateIsScanningCalled = false
21+
var didFailScanningBluetoothNotAvailableCalled = false
2022

2123
var lastBluetoothState: CBManagerState?
2224
var lastDeviceState: DeviceState?
@@ -58,4 +60,14 @@ final class MockBluetoothManagerDelegate: BluetoothManagerDelegate {
5860
didReceiveResultCalled = true
5961
lastResult = result
6062
}
63+
64+
func didUpdateIsScanning(_ isScanning: Bool) {
65+
didUpdateIsScanningCalled = true
66+
67+
}
68+
69+
func didFailScanningBluetoothNotAvailable() {
70+
didFailScanningBluetoothNotAvailableCalled = true
71+
}
72+
6173
}

0 commit comments

Comments
 (0)