How to wait until delegate method is called? - ios

I want to create a method startCentralManager that waits until the delegate method centralManagerDidUpdateState is called and returns a boolean value depending on the central state.
I want to use the method like this:
let centralManager = CentralManager()
let ready = centralManager.startCentralManager()
The CentralManager class should look something like this
class CentralManager: NSObject, CBCentralManagerDelegate {
var centralManager: CBCentralManager?
func startCentralManager() -> Bool {
centralManager = CBCentralManager.init(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(central: CBCentralManager) {
// startCentralManager() should return this
return central.state == CBCentralManagerState.PoweredOn
}
}
How can I solve this?
Thank you very much, in advance.
UPDATE:
There are some other methods, but the situation is almost equal. Another method is the following:
let connected = centralManager.connect<PARAMS>)
In this method if have to call
func scanForPeripheralsWithServices(_ serviceUUIDs: [CBUUID]?, options options: [String : AnyObject]?)
and wait until the delegate method
func centralManager(_ central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData advertisementData: [String : AnyObject], RSSI RSSI: NSNumber)
is called. Here, I have to call
func connectPeripheral(_ peripheral: CBPeripheral, options options: [String : AnyObject]?)
and again wait until the delegate method
func centralManager(_ central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral)
is called. In case of success, the call to centralManager.connect(<PARAMS>) should return true.

CBCentralManager works asychronously and you get informed of its status through a call to your delegate at some point in time that you have no direct control over.
You can't wait for asynchronous response between two lines of code. You'll have to design your program differently.
If you have a view controller that depends on the readiness of the CBCentralManager, you can make it the delegate and enable its peripheral related controls when your centralManagerDidUpdateState function is called.
for example (if you're using this for a scanner) :
class ScanningViewController:UIViewController,CBCentralManagerDelegate
{
var centralManager: CBCentralManager? = nil
override viewDidLoad()
{
centralManager = CBCentralManager.init(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(central: CBCentralManager)
{
if central.state == CBCentralManagerState.PoweredOn
{
// enable scanning controls here
}
}
}

I found a solution:
class CentralManager: NSObject, CBCentralManagerDelegate {
var centralManager: CBCentralManager?
var ready: Bool = false
func startCentralManager() -> Bool {
centralManager = CBCentralManager.init(delegate: self, queue: nil)
ready = false
while ready == false {
NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture())
}
return ready
}
func centralManagerDidUpdateState(central: CBCentralManager) {
ready = central.state == CBCentralManagerState.PoweredOn
}
}
Is this a good programming practice? Is there a better solution?
Thank you.

Create a timer that fires every X seconds
Inside the timer's selector check if connected and invalidate the timer or let it keep checking if not connected
Or consider using a semaphore and a dispatch_wait_forever

Related

Swift: how to stop exposing the delegate methods

WorkAround:
I am creating a utility class let's say BLEScanManager, which is responsible to scan nearby BLE devices. The only job of the utility class is to scan BLE devices and make a list of it.
The other classes can create an object of this BLEScanManager and get an array of BLE devices, like [Bluetooth] (here Bluetooth is a custom modal class).
Now to scan BLE devices, I have created the extension of BLEScanManager in the same class and override its delegate methods like below:
import UIKit
import CoreBluetooth
protocol BLEScanDelegate {
func reloadDeviceList()
}
internal class BLEScanManager: NSObject {
private var centralManager: CBCentralManager?
var devices : [Bluetooth] = []
var delegate: BLEScanDelegate?
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: .main)
}
// MARK:- Custom methods
func isScanning() -> Bool {
return centralManager?.isScanning ?? false
}
func stopScanning() {
centralManager?.stopScan()
}
func startScanning() {
devices.removeAll()
let options: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey:
NSNumber(value: false)]
centralManager?.scanForPeripherals(withServices: nil, options: options)
}
}
extension BLEScanManager : CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unknown: print("central.state is .unknown")
case .resetting: print("central.state is .resetting")
case .unsupported: print("central.state is .unsupported")
case .unauthorized: print("central.state is .unauthorized")
case .poweredOff: print("central.state is .poweredOff")
case .poweredOn: print("central.state is .poweredOn")
self.startScanning()
#unknown default: fatalError("unknow state")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("Discovered \(peripheral)")
var bluetooth = Bluetooth()
bluetooth.name = peripheral.name
bluetooth.identifier = peripheral.identifier.uuidString
bluetooth.state = peripheral.state.rawValue
bluetooth.advertisementData = advertisementData
let power = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? Double
let value: Double = pow(10, ((power ?? 0 - Double(truncating: RSSI))/20))
bluetooth.signalStrength = String(describing: value.round)
// Do not add duplicate device
let fitlerArray : [Bluetooth] = devices.filter { $0.identifier == bluetooth.identifier}
if fitlerArray.count == 0 {
devices.append(bluetooth)
}
self.delegate?.reloadDeviceList()
}
}
The thing here is these methods are exposed in the other classes too.
For example:
I have created an object of BLEScanManager in other class BLEListViewController to show a list of BLE device in UITableView.
class BLEListViewController: UITableViewController {
var scanManager: BLEScanManager!
}
I can access CBCentralManagerDelegate delegate methods in BLEListViewController class using an object scanManager.
Like below,
self.scanManager.centralManager?(<#T##central: CBCentralManager##CBCentralManager#>, didConnect: <#T##CBPeripheral#>)
This should expose the internal utility delegate methods to the outside world.
Question is how to stop exposing these delegates?
Please note that, if I use internal keyword it only hides that specific method. But it still allows accessing all other CBCentralManagerDelegate methods.
There’s nothing you can do about this. It is a problem with all optional protocol methods. The trouble is that optional protocol methods are an Objective C feature. You cannot hide such a method as private, because now Objective C cannot see it and so the method will never be discovered and called. It’s a flaw in the way Swift interfaces with Objective C. You just have to live with it.
What you need to do, is to hide behind a protocol - see the following example.
protocol Exposed {
func run()
}
struct Bluetooth {
func hidden() {
print("This method is hidden - ish")
}
}
extension Bluetooth: Exposed {
func run() {
print("this method is exposed")
}
}
struct BluetoothScanManager {
static func scan() -> [Exposed] {
return [Bluetooth(), Bluetooth()]
}
}
Now, when you run BluetoothScanManager you will get a list of exposed only with the run() method available, but behind the code it is a Bluetooth
for exposed in BluetoothScanManager.scan() {
exposed.run()
exposed.hidden() // not possible
}
Your only way to do this (which also is the correct way in my opinion) is to create a new private object inside the BLEScanManager class that implements the CBCentralManagerDelegate.
Than you can pass this object to the CBCentralManager init method.
If you then need to comunicate from this object to the BLEScanManager you can simply use another delegate with a private protocol so it doesn't show those methods outside of the class.
It would look something like this:
import UIKit
import CoreBluetooth
protocol BLEScanDelegate {
func reloadDeviceList()
}
internal class BLEScanManager: NSObject {
private var centralManager: CBCentralManager?
var devices : [Bluetooth] = []
var delegate: BLEScanDelegate?
let centralManagerDelegate = MyCentralManagerDelegateImplementation()
override init() {
super.init()
centralManagerDelegate.delegate = self
centralManager = CBCentralManager(delegate: centralManagerDelegate, queue: .main)
}
// MARK:- Custom methods
func isScanning() -> Bool {
return centralManager?.isScanning ?? false
}
func stopScanning() {
centralManager?.stopScan()
}
func startScanning() {
devices.removeAll()
let options: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey:
NSNumber(value: false)]
centralManager?.scanForPeripherals(withServices: nil, options: options)
}
}
private extension BLEScanManager: CentralManagerEventsDelegate {
func didUpdateState(){
// Here goes the switch with the startScanning
}
func didDiscoverPeripheral(...) {
// Here goes the logic on new peripheral and the call on self.delegate?.reloadDeviceList
}
}
internal protocol CentralManagerEventsDelegate: class {
func didUpdateState()
func didDiscoverPeripheral(...)
}
internal class MyCentralManagerDelegateImplementation: NSObject, CBCentralManagerDelegate {
weak var delegate: CentralManagerEventsDelegate?
func centralManagerDidUpdateState(_ central: CBCentralManager) {
self.delegate?.didUpdateState()
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
self.delegate?.didDiscoverPeripheral(actualParams)
}
}

BLE Connection between iPhone and iPad with JustWorks bonding

I'm currently working on a app that needs to send data from a iPhone to a iPad. I've been able to find the devices and connecting them by using both the CentralManager and PeriphiralManager. However right now it's showing me a message to asking to pair every single time. But when I press the "NO" option it will still bond by using JustWorks (at least it seems because when I go to bluetooth in settings it will just say "iPad" and I can't see information about the device other than that and it disappears from the list completely when I disconnect).
I was wondering how I could make sure the popup asking for pairing doesn't show up at all. I read somewhere to disable encryption on the Peripheral however I'm not sure how to do this when the iPad for instance is the Peripheral. The code I'm using right now looks as follows (this is the first time I'm working with corebluetooth and I'm currently still messing around with it and trying to get a grasp of how it works).
import UIKit
import CoreBluetooth
import CoreLocation
class ViewController: UIViewController {
#IBOutlet weak var receiveLabel: UILabel!
#IBOutlet weak var sendButton: UIButton!
var centralManager: CBCentralManager!
var peripheralManager: CBPeripheralManager!
var peripherals: [CBPeripheral] = []
var keepScanning: Bool = false
private var timerScanInterval: TimeInterval = 5
static let SERVICE_UUID = CBUUID(string: "4DF91029-B356-463E-9F48-BAB077BF3EF5")
static let RX_UUID = CBUUID(string: "3B66D024-2336-4F22-A980-8095F4898C42")
static let RX_PROPERTIES: CBCharacteristicProperties = .write
static let RX_PERMISSIONS: CBAttributePermissions = .writeable
override func viewDidLoad() {
super.viewDidLoad()
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
}
}
extension ViewController {
#IBAction func sendMessage(_ sender: UIButton) {
for per in peripherals {
centralManager.connect(per, options: nil)
}
}
func updateAdvertisingData() {
if peripheralManager.isAdvertising {
peripheralManager.stopAdvertising()
}
peripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey: [ViewController.SERVICE_UUID], CBAdvertisementDataLocalNameKey: "Test"])
}
func initService() {
let serialService = CBMutableService(type: ViewController.SERVICE_UUID, primary: true)
let rx = CBMutableCharacteristic(type: ViewController.RX_UUID, properties: ViewController.RX_PROPERTIES, value: nil, permissions: ViewController.RX_PERMISSIONS)
serialService.characteristics = [rx]
peripheralManager.add(serialService)
}
}
extension ViewController: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
centralManager.scanForPeripherals(withServices: [ViewController.SERVICE_UUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
break
default: break
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
peripheral.discoverServices(nil)
peripherals.append(peripheral)
}
}
extension ViewController: CBPeripheralDelegate, CBPeripheralManagerDelegate {
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state == .poweredOn {
initService()
updateAdvertisingData()
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
for service in peripheral.services! {
peripheral.discoverCharacteristics(nil, for: service)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
for characteristic in service.characteristics! {
let characteristic = characteristic as CBCharacteristic
let message = "TestMessage".data(using: .utf8)
peripheral.writeValue(message!, for: characteristic, type: CBCharacteristicWriteType.withResponse)
}
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
for request in requests {
if let value = request.value {
let messageText = String(data: value, encoding: String.Encoding.utf8)
receiveLabel.text = messageText
}
self.peripheralManager.respond(to: request, withResult: .success)
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices(nil)
}
}
Even though you only searched for peripherals that are advertising your specific service, when you discover a peripheral, you call
peripheral.discoverServices(nil)
This will discover all services on the discovered peripheral (which is another iOS device), not just your specific service.
Then, for each service you discover all characteristics and for each characteristic you discover you write to it with
let message = "TestMessage".data(using: .utf8)
peripheral.writeValue(message!, for: characteristic, type: CBCharacteristicWriteType.withResponse)
Since you have discovered all characteristics for all services on the device and then attempt to write to each one, if any of those characteristics require encryption you will trigger the pairing dialog. When you cancel the pairing dialog, that particular write will fail, but your app will keep on working (which is why you see the connection in settings).
You should refine your code so that it only discovers the specific service you are interested in and only attempts to write to your characteristic. This will prevent the pairing dialog from being triggered as your characteristic does not require encryption.

CoreBluetooth Functions not working from Singleton

So I currently got a bluetooth connection setup between a iPad and iPhone. I've created my testcode in the ViewController and everything works fine. Now I moved it to 2 manager classes one for the CBCentralManager and one for the CBPeripheralManager above those to classes I made a BluetoothManager which is a singleton class and holds some information regarding currently connected devices.
However when doing this I'm facing a problem it seems like the centralManager.connect() call doesn't actually work. I debugged my entire code and after that line nothing seems to happen and I can't seem to figure out why this is or where I'm actually going wrong.
The CentralManager class
import Foundation
import CoreBluetooth
class CentralManager: NSObject {
private var centralManager: CBCentralManager!
var peripherals: [CBPeripheral] = []
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
}
}
// MARK: - CBCentralManager Delegate Methods
extension CentralManager: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
centralManager.scanForPeripherals(withServices: [BLEConstants.serviceUUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
default:
break
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if !peripherals.contains(peripheral) {
peripheral.delegate = self
peripherals.append(peripheral)
centralManager.connect(peripheral, options: nil)
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.discoverServices([BLEConstants.serviceUUID])
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
guard let peripheralIndex = peripherals.index(of: peripheral), BluetoothManager.shared.deviceCharacteristic[peripheral] != nil else { return }
peripherals.remove(at: peripheralIndex)
BluetoothManager.shared.deviceCharacteristic.removeValue(forKey: peripheral)
}
}
// MARK: - CBPeripheral Delegate Methods
extension CentralManager: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
for service in peripheral.services! {
if service.uuid == BLEConstants.serviceUUID {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
for characteristic in service.characteristics! {
let characteristic = characteristic as CBCharacteristic
if BluetoothManager.shared.deviceCharacteristic[peripheral] == nil {
BluetoothManager.shared.deviceCharacteristic[peripheral] = characteristic
}
}
}
func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
}
}
The PeripheralManager class
class PeripheralManager: NSObject {
private var peripheralManager: CBPeripheralManager!
override init() {
super.init()
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
}
}
// MARK: - Manage Methods
extension PeripheralManager {
func updateAdvertising() {
guard !peripheralManager.isAdvertising else { peripheralManager.stopAdvertising(); return }
let advertisingData: [String: Any] = [CBAdvertisementDataServiceUUIDsKey: BLEConstants.serviceUUID,
CBAdvertisementDataLocalNameKey: BLEConstants.bleAdvertisementKey]
peripheralManager.startAdvertising(advertisingData)
}
func initializeService() {
let service = CBMutableService(type: BLEConstants.serviceUUID, primary: true)
let characteristic = CBMutableCharacteristic(type: BLEConstants.charUUID, properties: BLEConstants.charProperties, value: nil, permissions: BLEConstants.charPermissions)
service.characteristics = [characteristic]
peripheralManager.add(service)
}
}
// MARK: - CBPeripheralManager Delegate Methods
extension PeripheralManager: CBPeripheralManagerDelegate {
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state == .poweredOn {
initializeService()
updateAdvertising()
}
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
for request in requests {
if let value = request.value {
let messageText = String(data: value, encoding: String.Encoding.utf8)
print(messageText ?? "")
}
self.peripheralManager.respond(to: request, withResult: .success)
}
}
}
The BluetoothManager class
class BluetoothManager {
static let shared = BluetoothManager()
private var centralManager: CentralManager!
private var peripheralManager: PeripheralManager!
var deviceCharacteristic: [CBPeripheral: CBCharacteristic] = [:]
var connectedPeripherals: [CBPeripheral] { return centralManager.peripherals }
func setup() {
centralManager = CentralManager()
peripheralManager = PeripheralManager()
}
}
and then in my ViewController didLoad I'm calling BluetoothManager.shared.setup()
Does anyone know why the devices don't seem to connect with eachother or maybe the delegate functions after that just don't get called?
The process starts when the static shared variable is initialized with BluetoothManager(). I am not sure when this happens in Swift, it is either at the very start of the program, or when you use BluetoothManager.setup for the first time.
Initialization of the variable calls the init() method of the BluetoothManager. This will instantiate a CentralManager, and its init() method will be called. This will instantiate a CBCentralManager, which will start the Bluetooth process.
Then you call setup(), which will instantiate a new CentralManager, with its own CBCentralManager. I can imagine something goes wrong with two CBCentralManager.
To solve it, don't use setup(), but initialize the variables in init() instead.
To debug this kind of situation, put breakpoints in all init() methods. Create destructors, and put breakpoints in those as well. Technically, you need destructors anyways, because you need to remove yourself as delegate from the CBCentralManager object.
Note also that you only call scanForPeripherals from centralManagerDidUpdateState. The CBCentralManager can already be in poweredOn state when it starts, this can happen when another app is using Bluetooth at the same time - or when your first CBCentralManager object has already started it. In that case, centralManagerDidUpdateState will never be called.
Are you sure your Singleton is correctly initialized?
Try this:
import Foundation
private let singleton = Singleton()
class Singleton {
static let sharedInstance : Singleton = {
return singleton
}()
let cnetralManager = = CBCentralManager(delegate: self, queue: DispatchQueue.main)
}

Best way to manage BLE central object

I am developing an app which is capable of doing something like this.
My app should connects with BLE peripheral and i want to keep monitoring the connection. when disconnects the connection it should take the locatoin and update the map. i need to track the disconnection even if the peripheral's switch off event in my App.
i have two View Controllers. One is for update my Google map. other one is a tableViewController which is listed available device list. i have implemented a BLE Manager using Singolton pattern.
class BleManager: NSObject , CBCentralManagerDelegate, CBPeripheralDelegate {
var centralManager: CBCentralManager?
var peripherals = Array<CBPeripheral>()
var periparal : CBPeripheral!
let dataSaver = DataSaver.sharedInstance
let locationMgr = LocationInfo.sharedInstance
var viewController : CarList!
static let shared = BleManager()
//MARK:- Initialiser
private override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
}
init(vc : CarList) {
super.init()
viewController = vc
//centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
}
//MARK:- BLE Activities
func startScanForDevices() {
peripherals = []
//self.centralManager?.scanForPeripherals(withServices: [CBUUID(string: "DFB0")], options: nil)
self.centralManager?.scanForPeripherals(withServices: nil, options: nil)
//self.centralManager?.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey : true])
}
func stopScanForDevices() {
if centralManager != nil {
centralManager?.stopScan()
}
}
func connectToADevice(pheriperal : CBPeripheral){
periparal = pheriperal
// saving Connected Car in App LifeSycle
connectedCar = pheriperal
centralManager?.connect(pheriperal, options: nil)
(viewController != nil) ? viewController.devTable.reloadData() : print("")
}
func disconnectFromApp(pheriperal : CBPeripheral){
centralManager?.cancelPeripheralConnection(pheriperal)
}
//MARK:- BLE Central Delegates
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if (central.state == .poweredOn){
startScanForDevices()
}
else if (central.state == .poweredOff){
print("Powered Off")
centralManager?.cancelPeripheralConnection(periparal)
}
else if (central.state == .resetting){
print("resetting")
}
else if (central.state == .unauthorized){
print("unauthorized")
}
else if (central.state == .unsupported){
print("unsupported")
}else{
print("Unknown")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
//peripherals.append(peripheral)
//print("DEVICES LIST : \(peripherals)")
updatePheriperals(device: peripheral)
if viewController != nil {
viewController.devTable.reloadData()
if dataSaver.isAnyCarHasBeenSaved() == true && dataSaver.getMyCarIdentification() == peripheral.identifier.uuidString && peripheral.state == .disconnected{
connectToADevice(pheriperal: peripheral)
viewController.devTable.reloadData()
}
}else{
if dataSaver.isAnyCarHasBeenSaved() == true && dataSaver.getMyCarIdentification() == peripheral.identifier.uuidString{
connectToADevice(pheriperal: peripheral)
}
}
}
func updatePheriperals(device : CBPeripheral) {
if peripherals.count != 0 {
for dev in peripherals {
if device.identifier != dev.identifier {
peripherals.append(device)
}
}
}else{
peripherals.append(device)
}
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("\(ConnectionState(rawValue: peripheral.state.rawValue)?.name()) \(peripheral.name) from App.. Taking Geo cordinates")
locationMgr.didFindLocation = false
locationMgr.getCurrentLocation()
if viewController != nil {
viewController.devTable.reloadData()
}else{
print("Just Updating Locations")
}
showNotification(message: peripheral.name! + " has been Disconnected from Find My Car" )
startScanForDevices()
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("\(ConnectionState(rawValue: peripheral.state.rawValue)?.name()) \(peripheral.name) to App.. Removeing previous location")
dataSaver.removeLocation()
dataSaver.saveMyCarIdentification(myCar: peripheral.identifier.uuidString)
(viewController != nil) ? viewController.devTable.reloadData() : print("Just Updating Locations")
//print("Service UUIDs : \(peripheral)")
NotificationCenter.default.post(name: NSNotification.Name(rawValue: AppStrings.updateMapString), object: nil)
showNotification(message: peripheral.name! + " has been connected to Find My Car" )
}
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
print("Already Connected Pheriperals : \(dict[CBCentralManagerRestoredStatePeripheralsKey])")
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print("Failed To Connect with \(peripheral.name)")
centralManager?.cancelPeripheralConnection(peripheral)
}
In my MapViewController i use sharedInstance and start scanning
even in my TableViewController i do the same.
but sometimes when i try to switch off the peripheral device, my BLE Central does not called did disconnect peripheral delegate.
and i also wanna know how can i handle background scanning. with state prevention and restoration (I dont know anything about it. i just want to waork my app in background As same way in foreground)
Please help

KVO not working for attribute of custom NSObject subclass

I'm writing an app that interfaces with Bluetooth, and for OO reasons I need to be able to notify 1 to many objects when Bluetooth events happen. I've got a custom Model object:
class BluetoothModel: NSObject, CBCentralManagerDelegate {
var cBCentralManager: CBCentralManager!
var peripherals = [CBPeripheral]()
var count = 0
// MARK: - Singleton Definition
static let instance = BluetoothModel()
private override init() {
super.init()
cBCentralManager = CBCentralManager(delegate: self, queue: dispatch_get_global_queue(QOS_CLASS_UTILITY, 0))
}
// MARK: - CBCentralManagerDelegate
func centralManagerDidUpdateState(central: CBCentralManager) {
cBCentralManager.scanForPeripheralsWithServices(nil, options: nil)
}
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
if !peripherals.contains(peripheral) {
peripherals.append(peripheral)
count++
}
}
...
}
As well as the Controller I'm trying to hook up with KVO:
class MasterViewController: UITableViewController, CBCentralManagerDelegate {
...
override func viewDidLoad() {
super.viewDidLoad()
...
BluetoothModel.instance.addObserver(self, forKeyPath: "count", options: .New, context: nil)
}
...
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
print("Change!")
}
...
}
I've confirmed that count is being incremented repeatedly, even after the view controller loads and registers as observer. I'm pretty sure no notifications are firing, but I don't know why. Do I need to add something to enable KVO in a custom NSObject? I'm using the latest iOS9 and Swift2.
Make sure the count property is declared with the dynamic keyword. If it doesn't solve your problem, check this out.

Resources